ДЖСОН.Сеть для отображения пар имен и значений для перечислений

#c# #json.net

Вопрос:

Немного новичок здесь, использующий JSON.Нетто в C# —

 string JSONOutput = (JsonConvert.SerializeObject(product));
 

Когда я сериализую Product объект в своем решении, я получаю JSON, который выглядит следующим образом :

 {
   "product":{
      "displayStatus":1,
      "drugClass":1,
      "drugType":1
   }
}
 

Значения displayStatus , drugClass и drugType все из Enum s, поэтому вывод, который я действительно хочу, это :

 {
   "product":{
      "displayStatus":{
         "name":"Visible",
         "value":1
      },
      "drugClass":{
         "name":"Generic",
         "value":1
      },
      "drugType":{
         "name":"Drug",
         "value":1
      }
   }
}
 

Кто-нибудь знает, как это сделать? Я смотрел на StringEnumConverter , но единственное, что я вижу, это то, что он просто возвращает строку Enum вместо целочисленного значения — в то время как я хотел бы и то, и другое.

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

1. Вам нужно написать пользовательский конвертер Json. newtonsoft.com/json/help/html/CustomJsonConverter.htm

2. Зачем тебе и то, и другое? имя и ценность?

Ответ №1:

Обратите внимание, что на самом деле вам не должно понадобиться ничего подобного, есть несколько готовых способов преобразования перечислений. Например StringEnumConverter . Например

 [JsonConverter(typeof(StringEnumConverter))]
 

Однако из чистого нездорового любопытства и академического интереса

Учитывая преобразователь

 public class SomeConverter : JsonConverter 
{

   public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
   {
      writer.WriteStartObject();
      writer.WritePropertyName("name");
      writer.WriteValue(value.ToString());
      writer.WritePropertyName("value");
      writer.WriteValue(Convert.ChangeType(value,typeof(int)));
      writer.WriteEndObject();
   }

   public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
   {
      // ill leave this up to your imagination
      throw new NotImplementedException();
   }

   public override bool CanConvert(Type objectType)
   {
     return objectType == typeof(Enum);
   }
}
 

Перечисления

 public enum Bob
{
   one,
   two
}
public enum Blerge
{
   Asd,
   BFG
}
 

И Класс

 public class Product
{
   [JsonConverter(typeof(SomeConverter))]
   public Bob Bob { get; set; }

   [JsonConverter(typeof(SomeConverter))]
   public Blerge Blerge { get; set; }
}
 

Использование

 var asd = new Product()
{
   Bob = Bob.two,
   Blerge = Blerge.Asd
};

var bla = JsonConvert.SerializeObject(asd, Formatting.Indented);
Console.WriteLine(bla);
 

Выход

 {
  "Bob": {
    "name": "two",
    "value": 1
  },
  "Blerge": {
    "name": "Asd",
    "value": 0
  }
}
 

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

1. Насколько мне известно StringEnumConverter , не будет сериализовано как имя, так и значение. Как вы настраиваете это, чтобы излучать и то, и другое?

2. @PeterCsala правильно! нет, это не так, но дело в том, что вам все равно никогда не нужно сериализовывать/десериализовывать подобным образом

3. Я понял вашу точку зрения и согласен, но это требование операции.

Ответ №2:

Вы можете воспользоваться преимуществами JsonConverter<T> , подобными этому:

 public class EnumConverter<T> : JsonConverter<T> where T : System.Enum
{
    public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
        => throw new NotImplementedException();

    public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
    {
        var nameAndValue = new { Name = value.ToString("g"), Value = value.ToString("d") };
        var semiJson = JObject.FromObject(nameAndValue);
        semiJson.WriteTo(writer);
    }
}
 
  • Я ограничил T то, чтобы System.Enum
  • В WriteJson я создал анонимный тип и использовал строки формата перечисления для получения желаемых значений
  • Мне пришлось создать a JObject из анонимного типа, потому JsonWriter что он не поддерживает это напрямую

Затем вы можете украсить свой класс модели следующими атрибутами:

 public class Product
{
    [JsonConverter(typeof(EnumConverter<DisplayStatus>))]
    public DisplayStatus DisplayStatus { get; set; }
    [JsonConverter(typeof(EnumConverter<DrugClass>))]
    public DrugClass DrugClass { get; set; }
    [JsonConverter(typeof(EnumConverter<DrugType>))]
    public DrugType DrugType { get; set; }
}
 

И, наконец, логика сериализации выглядит следующим образом:

 var product = new Product() 
{
    DisplayStatus = DisplayStatus.Visible,
    DrugClass = DrugClass.Generic,
    DrugType = DrugType.Drug,
};
var result = JsonConvert.SerializeObject(
    new { Product = product },
    new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
 

result Будет выглядеть так:

 {
   "product":{
      "displayStatus":{
         "Name":"Visible",
         "Value":"1"
      },
      "drugClass":{
         "Name":"Generic",
         "Value":"1"
      },
      "drugType":{
         "Name":"Drug",
         "Value":"1"
      }
   }
}
 

Для того, чтобы закрепить корпус Name , и Value все, что вам нужно сделать serializer , это передать FromObject :

 var details = JObject.FromObject(nameAndValue, serializer);
 

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

 {
   "product":{
      "displayStatus":{
         "name":"Visible",
         "value":"1"
      },
      "drugClass":{
         "name":"Generic",
         "value":"1"
      },
      "drugType":{
         "name":"Drug",
         "value":"1"
      }
   }
}
 

UDPATE #1: Добавьте конвертеры в настройки

Как было указано в комментарии Product , класс не может быть изменен на JsonConverterAttribute s. Итак, если у вас есть это ограничение, вы можете преодолеть его, передав преобразователи через JsonSettings объект следующим образом:

 var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    Converters = new JsonConverter[] {
        new EnumConverter<DisplayStatus>(),
        new EnumConverter<DrugClass>(),
        new EnumConverter<DrugType>()
    }
};

var result = JsonConvert.SerializeObject(new { Product = product }, settings);
 

При таком подходе ваша модель будет свободна от атрибутов сериализации 🙂

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

1. Большое спасибо за ваше объяснение выше. Однако я не могу украсить свойства [JsonConverter(typeof(EnumConverter<DrugType>))] атрибутами, так как они являются частью другой сборки. Есть ли способ обойти это без использования атрибутов?

2. @DIamondGeezer Мы можем довольно легко преодолеть это ограничение. JsonSerializerSettings У него есть Converters свойство, в котором мы можем определить пользовательские преобразователи. Дайте мне минуту, и я обновлю сообщение.

3. @DIamondGeezer Я только что продлил его, пожалуйста, проверьте это.