C # Десериализует Json в модель и добавляет специальные свойства в список

#c# #json

#c# #json

Вопрос:

Я пытаюсь создать какой-то атрибут, который я мог бы добавить в класс модели, чтобы во время десериализации json, если ключ имеет определенный формат (в данном случае заканчивается на _special ), он был добавлен в список в модели.

ie: C#

 public class MyModel
{
    public string Name { get; set; }
    public IDictionary<string, string> SpecialFields { get; set; }
}
  

JSON

 {
    "Name": "John",
    "Height_special": "72",
    "Weight_special": "200"
}
  

Таким образом, результатом будет: MyModel.Name == "John" и MyModel.SpecialFields будут пары ключевых значений height и weight.

Возможно ли это?

РЕДАКТИРОВАТЬ: Проект использует .NET Core 3.1 и System.Text.Json

Комментарии:

1. Да, с помощью пользовательского конвертера JSON: newtonsoft.com/json/help/html/CustomJsonConverter.htm

2. Также, не могли бы вы предоставить немного больше деталей относительно того, где это будет использоваться? Похоже, что это веб-приложение или API; Я могу предположить, что это ASP.NET Ядро. Если да, то какая версия? Используете ли вы JSON.NET или новый System.Text.Json для десериализации? Из-за отсутствия этих деталей, вероятно, кто-то только что отклонил ваш пост и, возможно, что еще более важно, почему кому-то будет трудно вам помочь — нам нужно много работать наугад… Пожалуйста, проясните свой вопрос с немного более релевантными деталями . Спасибо!

3. Спасибо за помощь. Добавил эти детали к вопросу

4. Для System.Text.Json этого ресурса лучше: thinktecture.com/en/asp-net /…

Ответ №1:

Вы можете сделать это с помощью пользовательского конвертера JSON для System.Text.Json . Вот пример, который является расширяемым и подходит для вашего конкретного MyModel класса:

 public class MyModelJsonConverter : JsonConverter<MyModel>
{
    public override MyModel Read(ref Utf8JsonReader reader,
                                  Type typeToConvert,
                                  JsonSerializerOptions options)
    {
        MyModel model = new MyModel()
        {
            SpecialFields = new Dictionary<string, string>()
        };

        string nextPropertyName = string.Empty;

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonTokenType.PropertyName:
                    nextPropertyName = reader.GetString();
                    break;

                case JsonTokenType.String:
                    // Tempting to use reflection or switch here, but the if/else-if performs faster
                    if (nextPropertyName == "Name")
                    {
                        model.Name = reader.GetString();
                        continue;
                    }

                    // Make sure it's a _special field; if not, it's unrecognized!
                    int nameEndsAtIndex = nextPropertyName.IndexOf("_special");
                    if (nameEndsAtIndex == -1) throw new InvalidDataException("Unknown JSON field");

                    string key = nextPropertyName.Substring(0, nameEndsAtIndex);
                    string value = reader.GetString();

                    model.SpecialFields.Add(key, value);
                    break;
            }

        }

        return model;
    }

    public override void Write(Utf8JsonWriter writer,
                               MyModel value,
                               JsonSerializerOptions options)
    {
        writer.WritePropertyName("Name");
        writer.WriteStringValue(value.Name);

        foreach (var specialField in value.SpecialFields)
        {
            writer.WritePropertyName($"{specialField.Key}_special");
            writer.WriteStringValue(specialField.Value);
        }
    }
}
  

Это можно использовать следующим образом:

 var options = new JsonSerializerOptions() 
{ 
   Converters = { new MyModelJsonConverter() } 
};

var model = JsonSerializer.Deserialize<MyModel>(jsonString, options);
  

Обратите внимание, что этот пользовательский конвертер может выполнять обратное преобразование вашего MyModel класса:

 var jsonString = JsonSerializer.Serialize<MyModel>(model, options);
  

Для того, чтобы использовать эту настройку в ASP.NET Базовое приложение или веб-API, вам нужно добавить атрибут к вашему классу модели, чтобы позволить ASP.NET Core Framework знает, что вы собираетесь использовать пользовательский конвертер:

 [JsonConverter(typeof(MyModelJsonConverter))]
public class MyModel
{
    public string Name { get; set; }
    public IDictionary<string, string> SpecialFields { get; set; }
}
  

… и, точно так же, преобразование будет обработано автоматически!

Я настоятельно рекомендую вам пошагово изучить методы Read и Write с точками останова, чтобы действительно понять, что происходит под ними. Эти методы будут вызываться JsonSerializer напрямую; поняв, как они работают, вы сможете увидеть, как расширить этот пользовательский конвертер, если у вас возникнет такая необходимость.

Ответ №2:

Этого можно достичь с помощью нескольких шагов:

Создайте тип для полей Json:

 public class SpecialClass
{
    public string Name { get; set; }
    public string Height_Special { get; set; }
    public string Weight_Special { get; set; }
}
  

Преобразуйте Json с этим типом:

 var myObject = JsonConvert.DeserializeObject<SpecialClass>(yourJson);
  

Создайте словарь _special полей как [высота как ключ, вес как значение]:

 var dictionary = new Dictionary<string, string>()
{
    { myObject.Height_Special, myObject.Weight_Special }
};
  

Если требуется получить [ключ, значение] как [propertyName, значение]:

 var dictionary = new Dictionary<string, string>()
{
    { nameof(myObject.Height_Special), myObject.Height_Special },
    { nameof(myObject.Weight_Special), myObject.Weight_Special }
};
  

Таким образом, вы можете получить требуемые данные в MyModel виде:

 MyModel myModel = new MyModel()
{
    Name = myObject.Name,
    SpecialFields = dictionary,
};
  

Комментарии:

1. Это действительно вручную — почему бы не использовать пользовательский конвертер, разработанный для этой цели ?

2. Кроме того, почему ваш Dictionary<int, int> ? Вы имели в виду Dictionary<string, string> , как указано в OP в их сообщении? Почему вы отображаете ширину объекта на высоту ? OP хотел отобразить из имени свойства в значение свойства — не могли бы вы, пожалуйста, исправить свой пост, если планируете его сохранить?

3. @CoolBots Поднятый вами вопрос не имеет большого значения для использования int вместо string. Пользователь может это понять. В любом случае, я обновил ответ. И если пользовательский конвертер не используется, это не значит, что это решение неправильное. Это тоже работает. И если решение в классической форме, то это не веская причина для его отклонения.

4. Я отклонил это, потому что это было неправильно; вы это исправили, поэтому я удалил отрицательный отзыв. Ваш подход «классической формы» (что бы это ни значило) в данном случае сродни залезанию в машину через окно — он работает…