如何在json反序列化期间忽略未知的枚举值?

当我的枚举与json属性中提供的字符串值不匹配时,如何让Json.net不要抛出?

当我根据当前文档创建枚举时会发生这种情况,但第三方API稍后会添加更多枚举值。

我很高兴将特殊值标记为Unknown或使用可空的枚举,并且不匹配的值将返回null。

您可以使用自定义JsonConverter解决此问题。 这是我使用来自StringEnumConverter类中的一些部分放在一起的部分。 它应该为您提供灵活性,以您决定的方式处理事情。 以下是它的工作原理:

  • 如果在JSON中找到的值与枚举(作为字符串或整数)匹配,则使用该值。 (如果值为整数且有多个可能匹配,则使用第一个匹配。)
  • 否则,如果枚举类型可以为空,则将该值设置为null。
  • 否则,如果枚举具有名为“未知”的值,则使用该值。
  • 否则使用枚举的第一个值。

如果您不喜欢规则的工作方式,或者想要简化规则,请随意。 这是代码:

 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) 

如果你只关心反序列化,你可以做的另一个简单的事情是将枚举字段定义为字符串,并添加另一个’get’only字段,将字符串字段解析为其中一个已知值或“unknown”。 这个字段应该是’JsonIgnore’d。

通过对这个问题存在的一些建议,所有这些建议都使用StringEnumConverter作为主干,但没有建议通过inheritance使用它。 如果您的方案与我的相似,那么我正在接受第三方API响应,该响应具有大量可能的枚举值,可能会随着时间的推移而发生变化。 我只关心这些值中的10个,所以我希望所有其他值都回溯到默认值(如未知)。 这是我的枚举转换器来执行此操作:

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