NewtonSoft Десериализовать один и тот же объект из нескольких источников с разными именами свойств

Представьте, что у вас есть следующий класс

public class Person
{
public int Id {get; set;}
[JsonProperty("first_name")]
public string FirstName {get; set;}
[JsonProperty("last_name")]
public string LastName {get; set;}
}

У меня есть два источника JSON, которые я сейчас использую для JsonConvert.DeserializeObject<Person>(source). Это ставит меня в тупик. Оба источника должны иметь одинаковые имена свойств, чтобы это работало. Ради этого упражнения давайте предположим следующее для source.

источник 1:

{
"id" : 1,
"first_name": "Jon",
"last_name": "Doe"
} 

источник 2:

{
    "id" : 1,
    "firstName": "Jon",
    "lastName": "Doe"
} 

В этом сценарии второй источник не будет десериализован в объект Person, потому что имена свойств не совпадают.

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

Вариант № 1) Создайте отдельные промежуточные объекты для десериализации двух версий JSON, а затем сопоставьте эти объекты с вашим человеком. Вариант № 2) Создайте собственный конвертер для JSON, Пользовательский конвертер JSON

quaabaam 18.03.2022 20:29

Десериализуйте в Dictionary<string, object>, а затем заполните объект из него. В любом случае вам понадобится еще один набор сопоставлений между именем свойства JSON и именем свойства C#. Выбрать свой яд.

Heretic Monkey 18.03.2022 21:00
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
52
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Создайте некоторые свойства прокси только для установки (которые вы просто игнорируете везде, как следует из названия):

public class Person
{
  public int Id {get; set;}
  [JsonProperty("firstName")]
  public string FirstName {get; set;}
  [JsonProperty("lastName")]
  public string LastName {get; set;}
    
  [JsonProperty("first_name")]
  public string __Ignore1 { set { FirstName = value; } }
  [JsonProperty("last_name")]
  public string __Ignore2 { set { LastName = value; } }
}

Демонстрация скрипки здесь.

Я пытался использовать NamingStrategy, но, похоже, это можно настроить только по-другому. Я не вижу никакого вреда в этом маленьком хаке - будучи только установленными, они не сериализуются.

Это хороший трюк! Но это делает класс уродливым и может не пройти проверку кода.

vendettamit 18.03.2022 21:31

Конечно, выберите свой яд. Определенно зависит от проекта, команды, кодовой базы и т. д. Например, если это всего лишь небольшое консольное приложение или служба, анализирующая одну вещь — для любого члена моей команды вполне приемлемо кодировать это эффективно, как это. Однако, если это часть большого приложения, которое должно делать это для многих типов по аналогичному шаблону - следует использовать ваш общий подход (хотя я бы сделал более общий - удаление символов и нижний регистр).

TheSoftwareJedi 18.03.2022 22:13
Ответ принят как подходящий

Чтобы решить эту проблему с помощью пользовательского JsonConverter...

Реализуйте пользовательский JsonConverter, который определяет сопоставления имен полей, которые могут быть разными...

public class PersonConverter : JsonConverter
{
    private Dictionary<string, string> propertyMappings { get; set; }

    public PersonConverter()
    {
        this.propertyMappings = new Dictionary<string, string>
        {
            {"firstName","first_name"},
            {"lastName","last_name"},
        };
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object instance = Activator.CreateInstance(objectType);
        var props = objectType.GetTypeInfo().DeclaredProperties.ToList();

        JObject jo = JObject.Load(reader);
        foreach (JProperty jp in jo.Properties())
        {
            if (!propertyMappings.TryGetValue(jp.Name, out var name))
                    name = jp.Name;

            PropertyInfo prop = props.FirstOrDefault(pi =>
                    pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);

            prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
        }
        return instance;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.GetTypeInfo().IsClass;
    }

    public override bool CanWrite => false;
}

Добавьте атрибут JsonConverter в свой класс объектов, чтобы ваш пользовательский преобразователь использовался во время десериализации...

[JsonConverter(typeof(PersonConverter))]
public class RootObject
{
    [JsonProperty("first_name")]
    public string FirstName{ get; set; }

    [JsonProperty("last_name")]
    public string LastName { get; set; }
}

Чтобы использовать пользовательский конвертер...

string json_first_name = "{\"id\" : 1,\"first_name\": \"Jon\",\"last_name\": \"Doe\"}";
string json_firstname = "{\"id\" : 1,\"firstName\": \"Jon\",\"lastName\": \"Doe\"}";

var objFirst_Name = JsonConvert.DeserializeObject<RootObject>(json_first_name);
var objFirstName = JsonConvert.DeserializeObject<RootObject>(json_firstname);

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

public class Person
{
    public int Id {get; set;}
    //[JsonProperty("first_name")]
    public string FirstName {get; set;}
    //[JsonProperty("last_name")]
    public string LastName {get; set;}
}

private static void PrintDeserializedObject(string obj, DefaultContractResolver resolver) 
{
    var person = JsonConvert.DeserializeObject<Person>(obj, new JsonSerializerSettings
    {
        ContractResolver = resolver,
        
    });
    Console.WriteLine("{0}.{1}.{2}", person.Id, person.FirstName, person.LastName);
}

public static void Main()
{
    var firstObj = @"{
        ""id"" : 1,
        ""first_name"": ""Jon"",
        ""last_name"": ""Doe""
        }";
    
    var secondObj = @"{
        ""id"" : 1,
        ""firstName"": ""Jon"",
        ""lastName"": ""Doe""
        }"; 
    
    DefaultContractResolver snakeResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    };

    PrintDeserializedObject(firstObj, snakeResolver);
    PrintDeserializedObject(secondObj, new CamelCasePropertyNamesContractResolver());
}

Идея состоит в том, чтобы использовать ContractResolver и NamingStrategy. Что касается верблюжьего корпуса, то там встроенный CamelCasePropertyNamesContractResolver. Вместо этого для случая со змеей вам нужно создать новый DefaultContractResolver со стратегией именования, установленной на SnakeCaseNamingStrategy.

Обратите внимание, что в этом случае вы можете избавиться от JsonPropertyAttribute, по крайней мере, я не нашел способа переопределить их из JsonSerializerSettings.

Попробуйте скрипку.

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