Search code examples
c#listgenericsmapping

The problem of mapping in creating a Generic Method


This Is My Models :

  public class Result1 
  { 
      public string Price { get; set; }
      public string Id { get; set; } 
  }
  public class Result2
  { 
      public string firstName { get; set; } 
      public string lastName { get; set; }
      public string Id { get; set; } 
  }
  public class JsonResult1
  { 
      public List<Product> ProductLists { get; set; } 
  }
  public class JsonResult2 
  {
      public List<User> UserLists { get; set; } 
  }

And This is My First And Second Methods

   public async Task<List<Result1>> GetFirstMethod(string id, string a)
   {
       var info = JsonConvert.DeserializeObject<JsonResult1>(a);

       var mylist = new List<Result1>();
       foreach (var item in info.ProductLists)
       {
           mylist.Add(new Result1()
           {
               Price = item.Price,
               Id = item.ID
           });
       }
       return mylist;
   }
   public async Task<List<Result2>> GetSecondMethod(string id, string a)
   {
       var info = JsonConvert.DeserializeObject<JsonResult2>(a);

       var mylist = new List<Result2>();
       foreach (var item in info.UserLists)
       {
           mylist.Add(new Result2()
           {
               firstName = item.FirstName,
               lastName = item.LastName,
               Id = item.ID
           });
       }
       return mylist;
   }
//And the third method and the fourth method and...

Each method has different models...

How to make a generic method for All the above methods??

The main problem is in the mapping part inside the list...

Is it even possible to do something like this?


Solution

  • This is the most probably convoluted answer imaginable, but you can use a dictionary to keep delegates to map between the three different types:

    public class Mapping {
        public static readonly Dictionary<(Type,Type), Mapping> RegisteredMappings = new();
        public Func<object,IEnumerable<object>> enumerate;
        public Func<object,object> map;
        
        public static void Register<T,M,R>(Func<T, IEnumerable<M>> enumerateFunc, Func<M, R> mappingFunc) where T : class where M : class where R : class {
            RegisteredMappings.Add((typeof(T), typeof(R)), new Mapping {
                enumerate = (object input) => enumerateFunc((T) input),
                map = (object input) => mappingFunc((M) input),
            });
        }
    }
    
    public static List<R> Get<T,R>(string id, string a) {
        if (!Mapping.RegisteredMappings.TryGetValue((typeof(T), typeof(R)), out Mapping mapping)) {
            throw new ArgumentException($"No mapping exists between {typeof(T).Name} and {typeof(R).Name}");
        }
    
        T info = JsonConvert.DeserializeObject<T>(a);
        List<R> list = new();
    
        foreach (var item in mapping.enumerate(info)) {
            list.Add((R) mapping.map(item));
        }
    
        return list;
    }
    

    Example usage:

    public static void Main()
    {
        // Example JSON for JsonResult1 (Product) and JsonResult2 (User)
        string productJson = "{\"ProductLists\": [{\"Price\": \"100.00\", \"ID\": \"XYZ\"},{\"Price\": \"50.00\", \"ID\": \"ABC\"}]}";
        string userJson = "{\"UserLists\": [{\"FirstName\": \"Mark\", \"LastName\": \"B.\", \"ID\": \"XYZ\"},{\"FirstName\": \"Joseph\", \"LastName\": \"S.\", \"ID\": \"ABC\"}]}";
    
        // Register the mapping from JsonResult1 -> Result1 and JsonResult2 -> Result2 (using the Product/User classes as an intermediary)
        Mapping.Register<JsonResult1, Product, Result1>(EnumerateProducts, MapProduct);
        Mapping.Register<JsonResult2, User, Result2>(EnumerateUsers, MapUser);
    
        // Example usage
        List<Result1> productResults = Get<JsonResult1, Result1>("unused", productJson);
        Console.WriteLine(string.Join(',', productResults.Select(product => product.Price)));
        // -> 100.00,50.00
    
        List<Result2> userResults = Get<JsonResult2, Result2>("unused", userJson);
        Console.WriteLine(string.Join(',', userResults.Select(user => user.firstName)));
        // -> Mark,Joseph
    }
    
    public static IEnumerable<Product> EnumerateProducts(JsonResult1 jr1) => jr1.ProductLists;
    public static Result1 MapProduct(Product p) {
        return new Result1 {
            Price = p.Price,
            Id = p.ID,
        };
    }
    public static IEnumerable<User> EnumerateUsers(JsonResult2 jr2) => jr2.UserLists;
    public static Result2 MapUser(User u) {
        return new Result2 {
            firstName = u.FirstName,
            lastName = u.LastName,
            Id = u.ID
        };
    }
    

    This way you only need to register each mapping once on boot, and then you never have to reference them again. Simply calling the function with the input/result types specified will work.