Проблема отображения при создании универсального метода

Это мои модели:

  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; } 
  }

И это мои первый и второй методы

   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...

Каждый метод имеет разные модели...

Как сделать общий метод для всех вышеперечисленных методов??

Основная проблема заключается в части сопоставления внутри списка...

Возможно ли вообще сделать что-то подобное?

«Основная проблема заключается в части сопоставления внутри списка». Вы согласны изменить список параметров вашего метода, чтобы передать Func<> для выполнения этого сопоставления?

gunr2171 10.07.2024 19:06

Я не уверен, какую выгоду вы получите, создав один общий метод. Вам необходимо определить, как выполнять сопоставление (выбирая свойство «Список» из переменной info и сопоставляя каждую запись списка из источника с выходным списком) где-то. Это может быть в их собственных методах, или это должен сделать вызывающий объект. Вы всегда можете извлечь анализ JSON, и вместо этого вызывающий объект может передать анализируемый объект.

gunr2171 10.07.2024 19:11

@gunr2171 Gunr2171 Я согласен с любым решением, которое решает проблему. Только я не могу изменить классы «Result» и «JsonResult».

Ya YA 10.07.2024 19:11

Ну, а User и а Product — совершенно разные вещи, не имеющие абсолютно ничего общего. Другими словами: в ваших моделях нет ничего общего.

MakePeaceGreatAgain 10.07.2024 19:16

@MakePeaceGreatAgain Точно.

Ya YA 10.07.2024 19:17

Только я не могу изменить классы «Result» и «JsonResult», тогда ничего не поделаешь.

Ivan Petrov 10.07.2024 19:30

@gunr2171 Преимущество в том, что у меня будет универсальный метод! В настоящее время у меня есть около 10 методов, все из которых аналогичны описанным выше... Могу ли я получить помощь от AutoMapper или Mapster?

Ya YA 10.07.2024 19:33

@IvanPetrov То есть, по-вашему, это невозможно?

Ya YA 10.07.2024 19:34

@YaYA, я думаю, нет, но если кто-нибудь предложит оригинальное и удобное в обслуживании решение с учетом этих ограничений

Ivan Petrov 10.07.2024 19:36

Итак, если метод будет универсальным, где будет храниться информация о сопоставлении? Как узнать, какое свойство перечислять и как преобразовать каждый элемент списка? На мой взгляд, у вас либо есть 10 методов со своей собственной логикой для преобразования своего особого типа данных, либо в каждом месте, где вы вызываете один общий метод, вам нужно указать эту логику преобразования.

gunr2171 10.07.2024 19:49

@gunr2171 Gunr2171 Что ты думаешь об ответе ipodtouch0218?

Ya YA 10.07.2024 21:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
11
69
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Используйте один универсальный метод с отражением для динамического сопоставления свойств JSON с объектами модели, избегая нескольких реализаций MapFromJson. Это централизует и упрощает процесс сопоставления различных типов.

public interface IJsonResult
{
}


public class JsonResult1 : IJsonResult
{
    public List<Product> ProductLists { get; set; }
}

public class JsonResult2 : IJsonResult
{
    public List<User> UserLists { get; set; }
}

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 Product
{
    public string Price { get; set; }
    public string ID { get; set; }
}

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string ID { get; set; }
}

public class DataService
{
    public async Task<List<T>> GetMethod<T, J>(string id, string jsonString) where J : IJsonResult
    {
        return await Task.Run(() => MapFromJson<T, J>(jsonString));
    }

    private List<T> MapFromJson<T, J>(string jsonString) where J : IJsonResult
    {
        var info = JsonConvert.DeserializeObject<J>(jsonString);
        var mylist = new List<T>();

        // Use reflection to get the lists and map to the result type
        var listProperty = typeof(J).GetProperties().FirstOrDefault(p => p.PropertyType.IsGenericType);
        if (listProperty != null)
        {
            var itemList = (IEnumerable<object>)listProperty.GetValue(info);

            foreach (var item in itemList)
            {
                var result = Activator.CreateInstance<T>();

                foreach (var prop in typeof(T).GetProperties())
                {
                    var itemProp = item.GetType().GetProperty(prop.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                    if (itemProp != null)
                    {
                        prop.SetValue(result, itemProp.GetValue(item));
                    }
                }

                mylist.Add(result);
            }
        }

        return mylist;
    }
}

// Usage
public async Task ExampleUsage()
{
    var dataService = new DataService();

    string jsonString1 = ""; // Your JSON string for Result1
    List<Result1> result1List = await dataService.GetMethod<Result1, JsonResult1>("someId", jsonString1);

    string jsonString2 = ""; // Your JSON string for Result2
    List<Result2> result2List = await dataService.GetMethod<Result2, JsonResult2>("someId", jsonString2);
}

Я не вижу, как это может быть улучшением. Насколько я понимаю, ОП не хочет создавать X много методов MapFromJson, они хотят создать один общий MapFromJson.

gunr2171 10.07.2024 19:17

Спасибо за написанный вами код и потраченное время... но проблема не решена... "new Result2Mapper();" Это само по себе проблема

Ya YA 10.07.2024 19:36

@gunr2171 Спасибо за отзыв, да, я понял, исправлю.

Behtash 10.07.2024 20:03

@YaYA Я обновил код, решит ли это вашу проблему?

Behtash 10.07.2024 20:05

@Бехташ, дай мне попробовать

Ya YA 10.07.2024 21:17

Мы можем сделать следующее (некрасиво, но изменить нельзя JsonResult*)

public async Task<List<TResult>> GetGenericMethod
<TResult, TJsonResult, TItem>(
string id, string a, Func<TItem, TResult> mapper
, Func<TJsonResult, IEnumerable<TItem>> getIterator)
 {
    TJsonResult info = JsonConvert.DeserializeObject<TJsonResult>(a);
    // shorter version
    //return getIterator(info)
    //.Select(mapper)
    //.ToList();
    List<TResult> mylist = new List<TResult>();
    IEnumerable<TItem> enumerable = getIterator(info);
    foreach (var item in enumerable) {
        mylist.Add(mapper(item));
    }
    return mylist;
}

Пример использования:

var jsonResult1 = new JsonResult1();
jsonResult1.ProductLists = new List<Product>();
jsonResult1.ProductLists.Add(new Product { ID = "3", Price = "4" });
jsonResult1.ProductLists.Add(new Product { ID = "7", Price = "8" });

var serialized = JsonConvert.SerializeObject(jsonResult1);
Func<Product, Result1> mapper = p => new Result1 {
    Price = p.Price,
    Id = p.ID
}; // this can be a method and not a lambda

var result = await GetFirstMethod<Result1, JsonResult1, Product>(
"", serialized, mapper, r => r.ProductLists);
foreach (var element in result) {
    Console.WriteLine(element);
}

Первоначально я использовал производный тип от JSonResult1, который реализовал IEnumerable<T>, чтобы использовать перечислитель, но, вдохновленный другим ответом (от ipodtouch0218), в этом, похоже, нет реальной необходимости, и мы можем просто использовать Func<TJsonResult, IEnumerable<TItem>> для менее многословного и так же, как функциональный код.

Спасибо за ваше время. Позвольте мне провести небольшой тест

Ya YA 10.07.2024 21:22
Ответ принят как подходящий

Вероятно, это самый запутанный ответ, который только можно себе представить, но вы можете использовать словарь, чтобы делегаты могли сопоставлять три разных типа:

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;
}

Пример использования:

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
    };
}

Таким образом, вам нужно будет зарегистрировать каждое сопоставление только один раз при загрузке, и тогда вам больше никогда не придется ссылаться на них. Просто вызов функции с указанными типами ввода/результата будет работать.

Позвольте мне провести небольшой тест, это действительно запутанно.

Ya YA 10.07.2024 21:16

Это запутанно, потому что вы не можете изменять свои классы и нет способа получить доступ к значениям общим/унаследованным способом. Вероятно, это лучшее, что вы можете сделать со своими ограничениями. (Вам нужно указать функции Json->Class и Class->Result только один раз)

ipodtouch0218 10.07.2024 21:18

Спасибо, ваш ответ подтвержден. Весь мой код (все методы) был изменен так, как вы написали... только в одном случае у меня возникла проблема... один из моих методов имеет JsonResult с двумя списками, и мне нужны оба списка. ..но ваш код не позволяет мне использовать более 1 списка. Что мне делать в этом исключении?

Ya YA 12.07.2024 13:42

Я бы сказал, что самый простой способ - объединить списки в один внутри вашей функции перечисления: IEnumerable<User> EnumerateUsers(JsonResult2 jr2) { List<User> results = new(); results.AddRange(jr2.UserGroupA); results.AddRange(jr2.UserGroupB); return results; } или, если это двухмерный массив, вы можете использовать IEnumerable<User> EnumerateUsers(JsonResult2 jr2) => jr2.TwoDimensionalUsersArray.SelectMany(u => u);

ipodtouch0218 12.07.2024 15:48

UserGroupA и UserGroupB завершают разные вещи... results.AddRange(jr2.UserGroupA) Хорошо. Далее мне нужно сделать что-то вроде этого: results.ForEach(y => y.Name = (UserGroupB.FirstOrDefault(x => x.Id == y.ProductRef)).Name);

Ya YA 12.07.2024 19:27

«UserGroupA и UserGroupB совершенно разные вещи», код в моем ответе обрабатывает только преобразование между TypeA -> MiddlemanType -> TypeB. Если у вас есть две разные вещи, вам понадобится совершенно другая функция (которая не была указана в качестве примера в исходном вопросе).

ipodtouch0218 12.07.2024 19:30

Хорошо, спасибо. Ваш код великолепен. Это исключение (1 из 10 методов). Можно ли изменить ваш код с помощью Mapster?

Ya YA 12.07.2024 20:17

Другие вопросы по теме