Как игнорировать неизвестные значения enum во время десериализации json?

Как я могу заставить Json.net не бросать, когда мое перечисление не соответствует строковому значению, предоставленному в свойстве json?

Это происходит, когда я создаю enum на основе текущей документации, но сторонний API добавляет больше значений enums позже.

Я был бы доволен тем, что отмечен особый параметр как Unknown или с помощью enums с нулевым значением, и непревзойденное значение возвращает null.

Вы можете решить эту проблему с помощью настраиваемого JsonConverter . Вот один из них, который я собрал, используя несколько частей из classа StringEnumConverter который поступает из Json.Net. Это должно дать вам гибкость в том, чтобы обращаться с вещами независимо от того, как вы решаете. Вот как это работает:

  • Если значение, найденное в JSON, соответствует перечислению (либо как строка, либо целое число), это значение используется. (Если значение целое и существует несколько возможных совпадений, используется первый из них).
  • В противном случае, если тип enums имеет значение NULL, значение равно null.
  • В противном случае, если перечисление имеет значение, называемое «Неизвестно», это значение используется.
  • В противном случае используется первое значение enums.

Если вам не нравится, как работают правила, или вы хотите упростить его, не стесняйтесь. Вот код:

 class TolerantEnumConverter : JsonConverter { public override bool CanConvert(Type objectType) { Type type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType; return type.IsEnum; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { bool isNullable = IsNullableType(objectType); Type enumType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType; string[] names = Enum.GetNames(enumType); if (reader.TokenType == JsonToken.String) { string enumText = reader.Value.ToString(); if (!string.IsNullOrEmpty(enumText)) { string match = names .Where(n => string.Equals(n, enumText, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (match != null) { return Enum.Parse(enumType, match); } } } else if (reader.TokenType == JsonToken.Integer) { int enumVal = Convert.ToInt32(reader.Value); int[] values = (int[])Enum.GetValues(enumType); if (values.Contains(enumVal)) { return Enum.Parse(enumType, enumVal.ToString()); } } if (!isNullable) { string defaultName = names .Where(n => string.Equals(n, "Unknown", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (defaultName == null) { defaultName = names.First(); } return Enum.Parse(enumType, defaultName); } return null; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteValue(value.ToString()); } private bool IsNullableType(Type t) { return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)); } } 

Вот демоверсия, которая превращает конвертер в свои ряды, используя пару разных перечислений (у одного есть значение «Неизвестно», а другое нет):

 [JsonConverter(typeof(TolerantEnumConverter))] enum Status { Ready = 1, Set = 2, Go = 3 } [JsonConverter(typeof(TolerantEnumConverter))] enum Color { Red = 1, Yellow = 2, Green = 3, Unknown = 99 } class Foo { public Status NonNullableStatusWithValidStringValue { get; set; } public Status NonNullableStatusWithValidIntValue { get; set; } public Status NonNullableStatusWithInvalidStringValue { get; set; } public Status NonNullableStatusWithInvalidIntValue { get; set; } public Status NonNullableStatusWithNullValue { get; set; } public Status? NullableStatusWithValidStringValue { get; set; } public Status? NullableStatusWithValidIntValue { get; set; } public Status? NullableStatusWithInvalidStringValue { get; set; } public Status? NullableStatusWithInvalidIntValue { get; set; } public Status? NullableStatusWithNullValue { get; set; } public Color NonNullableColorWithValidStringValue { get; set; } public Color NonNullableColorWithValidIntValue { get; set; } public Color NonNullableColorWithInvalidStringValue { get; set; } public Color NonNullableColorWithInvalidIntValue { get; set; } public Color NonNullableColorWithNullValue { get; set; } public Color? NullableColorWithValidStringValue { get; set; } public Color? NullableColorWithValidIntValue { get; set; } public Color? NullableColorWithInvalidStringValue { get; set; } public Color? NullableColorWithInvalidIntValue { get; set; } public Color? NullableColorWithNullValue { get; set; } } class Program { static void Main(string[] args) { string json = @" { ""NonNullableStatusWithValidStringValue"" : ""Set"", ""NonNullableStatusWithValidIntValue"" : 2, ""NonNullableStatusWithInvalidStringValue"" : ""Blah"", ""NonNullableStatusWithInvalidIntValue"" : 9, ""NonNullableStatusWithNullValue"" : null, ""NullableStatusWithValidStringValue"" : ""Go"", ""NullableStatusWithValidIntValue"" : 3, ""NullableStatusWithNullValue"" : null, ""NullableStatusWithInvalidStringValue"" : ""Blah"", ""NullableStatusWithInvalidIntValue"" : 9, ""NonNullableColorWithValidStringValue"" : ""Green"", ""NonNullableColorWithValidIntValue"" : 3, ""NonNullableColorWithInvalidStringValue"" : ""Blah"", ""NonNullableColorWithInvalidIntValue"" : 0, ""NonNullableColorWithNullValue"" : null, ""NullableColorWithValidStringValue"" : ""Yellow"", ""NullableColorWithValidIntValue"" : 2, ""NullableColorWithNullValue"" : null, ""NullableColorWithInvalidStringValue"" : ""Blah"", ""NullableColorWithInvalidIntValue"" : 0, }"; Foo foo = JsonConvert.DeserializeObject(json); foreach (PropertyInfo prop in typeof(Foo).GetProperties()) { object val = prop.GetValue(foo, null); Console.WriteLine(prop.Name + ": " + (val == null ? "(null)" : val.ToString())); } } } 

Вывод:

 NonNullableStatusWithValidStringValue: Set NonNullableStatusWithValidIntValue: Set NonNullableStatusWithInvalidStringValue: Ready NonNullableStatusWithInvalidIntValue: Ready NonNullableStatusWithNullValue: Ready NullableStatusWithValidStringValue: Go NullableStatusWithValidIntValue: Go NullableStatusWithInvalidStringValue: (null) NullableStatusWithInvalidIntValue: (null) NullableStatusWithNullValue: (null) NonNullableColorWithValidStringValue: Green NonNullableColorWithValidIntValue: Green NonNullableColorWithInvalidStringValue: Unknown NonNullableColorWithInvalidIntValue: Unknown NonNullableColorWithNullValue: Unknown NullableColorWithValidStringValue: Yellow NullableColorWithValidIntValue: Yellow NullableColorWithInvalidStringValue: (null) NullableColorWithInvalidIntValue: (null) NullableColorWithNullValue: (null) 

Если вы только заботитесь о десериализации, еще одна простая вещь, которую вы можете сделать, это определить поле enums в виде строки и добавить другое поле «get», которое анализирует поле строки либо на одно из известных значений, либо на «неизвестно». Это поле должно быть «JsonIgnore’d».

Просмотрев несколько предложений, которые существуют для этой проблемы, все они используют StringEnumConverter в качестве основы, но никакие предложения не используют его через наследование. Если ваш сценарий был похож на мой, я принимал сторонний ответ API, который имеет массу возможных значений enums, которые могут меняться со временем. Мне все равно, может быть, 10 из этих значений, поэтому все остальные значения я хочу отменить по умолчанию (например, Unknown). Вот мой конвертер enum для этого:

 ///  ///  /// Defaults enum values to the base value if ///  public class DefaultUnknownEnumConverter : StringEnumConverter { ///  /// The default value used to fallback on when a enum is not convertable. ///  private readonly int defaultValue; ///  ///  /// Default constructor. Defaults the default value to 0. ///  public DefaultUnknownEnumConverter() {} ///  ///  /// Sets the default value for the enum value. ///  /// The default value to use. public DefaultUnknownEnumConverter(int defaultValue) { this.defaultValue = defaultValue; } ///  ///  /// Reads the provided JSON and attempts to convert using StringEnumConverter. If that fails set the value to the default value. ///  /// Reads the JSON value. /// Current type that is being converted. /// The existing value being read. /// Instance of the JSON Serializer. /// The deserialized value of the enum if it exists or the default value if it does not. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { try { return base.ReadJson(reader, objectType, existingValue, serializer); } catch { return Enum.Parse(objectType, $"{defaultValue}"); } } ///  ///  /// Validates that this converter can handle the type that is being provided. ///  /// The type of the object being converted. /// True if the base class says so, and if the value is an enum and has a default value to fall on. public override bool CanConvert(Type objectType) { return base.CanConvert(objectType) && objectType.GetTypeInfo().IsEnum && Enum.IsDefined(objectType, defaultValue); } } 

Использование аналогично другим примерам:

 [JsonConverter(typeof(DefaultUnknownEnumConverter))] public enum Colors { Unknown, Red, Blue, Green, } [JsonConverter(typeof(DefaultUnknownEnumConverter), (int) NotFound)] public enum Colors { Red = 0, Blue, Green, NotFound } 
  • JSON.Net Xml Сериализация неправильно понимает массивы
  • Как сериализовать словарь как часть его родительского объекта с помощью Json.Net
  • Игнорирование нулевых полей в Json.net
  • Настройка пользовательских дат WebApi Json.NET
  • Обнаружен цикл саморегуляции - Возврат данных из WebApi в браузер
  • Исключение «Исключительная привязка к реляционной ссылке» с JSON.Net
  • Json.NET Отключить десериализацию в DateTime
  • Указание пользовательского формата DateTime при сериализации с помощью Json.Net
  • Как конвертировать JSON в XML или XML в JSON?
  • Уничтожить stream массива json по одному элементу за раз
  • Дезаминирование гетерогенного массива JSON в ковариантный список с использованием JSON.NET
  • Давайте будем гением компьютера.