【问题标题】:How can I ignore unknown enum values during json deserialization?如何在 json 反序列化期间忽略未知的枚举值?
【发布时间】:2014-03-31 01:03:39
【问题描述】:

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

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

我很乐意将特殊值标记为未知或使用可为空的枚举,不匹配的值将返回空值。

【问题讨论】:

    标签: c# json.net


    【解决方案1】:

    您可以使用自定义JsonConverter 解决此问题。这是我使用来自 Json.Net 的 StringEnumConverter 类中的几部分组合而成的。它应该使您可以灵活地处理任何您决定的事情。以下是它的工作原理:

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

    这里是代码。随意更改它以满足您的需求。

    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<Foo>(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)
    

    【讨论】:

    • 我以为会有一些内置的东西。听起来很常见。
    • Json.Net 附带一个StringEnumConverter,但如果在枚举中找不到字符串值,它会抛出异常。在我选择自定义转换器之前,我确实检查了这一点。如果你想要更宽松的东西,看来你必须自己写。
    • 看起来不错,我能想到的唯一增强功能——添加对EnumMember属性的支持。
    • 我已经扩展了这个版本,增加了EnumMember 支持,它可以在这里找到:gist.github.com/gubenkoved/999eb73e227b7063a67a50401578c3a7
    【解决方案2】:

    查看针对此问题存在的少数建议,它们都使用 StringEnumConverter 作为主干,但没有建议通过继承使用它。如果你的场景和我的一样,我正在接受第 3 方 API 响应,它有大量可能的枚举值,这些值可能会随着时间而改变。我只关心这些值中的 10 个,所以我想使用默认值(如未知)的所有其他值。这是我的枚举转换器:

    /// <inheritdoc />
    /// <summary>
    /// Defaults enum values to the base value if 
    /// </summary>
    public class DefaultUnknownEnumConverter : StringEnumConverter
    {
        /// <summary>
        /// The default value used to fallback on when a enum is not convertable.
        /// </summary>
        private readonly int defaultValue;
    
        /// <inheritdoc />
        /// <summary>
        /// Default constructor. Defaults the default value to 0.
        /// </summary>
        public DefaultUnknownEnumConverter() 
        {}
    
        /// <inheritdoc />
        /// <summary>
        /// Sets the default value for the enum value.
        /// </summary>
        /// <param name="defaultValue">The default value to use.</param>
        public DefaultUnknownEnumConverter(int defaultValue)
        {
            this.defaultValue = defaultValue;
        }
    
        /// <inheritdoc />
        /// <summary>
        /// Reads the provided JSON and attempts to convert using StringEnumConverter. If that fails set the value to the default value.
        /// </summary>
        /// <param name="reader">Reads the JSON value.</param>
        /// <param name="objectType">Current type that is being converted.</param>
        /// <param name="existingValue">The existing value being read.</param>
        /// <param name="serializer">Instance of the JSON Serializer.</param>
        /// <returns>The deserialized value of the enum if it exists or the default value if it does not.</returns>
        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}");
            }
        }
    
        /// <inheritdoc />
        /// <summary>
        /// Validates that this converter can handle the type that is being provided.
        /// </summary>
        /// <param name="objectType">The type of the object being converted.</param>
        /// <returns>True if the base class says so, and if the value is an enum and has a default value to fall on.</returns>
        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
    }
    

    【讨论】:

    • 一个小补充。您还可以在 catch 块中添加以下行来处理可空枚举的反序列化: var underlyingType = Nullable.GetUnderlyingType(objectType); if (underlyingType != null) return Enum.Parse(underlyingType , $"{defaultValue}") return Enum.Parse(objectType, $"{defaultValue}");
    【解决方案3】:

    如果您只关心反序列化,您可以做的另一件简单的事情是将枚举字段定义为字符串,并添加另一个仅“获取”字段,将字符串字段解析为已知值或“未知”。该字段应为“JsonIgnore”。

    【讨论】:

      【解决方案4】:

      您可以使用自定义的 StringEnumConverter,如下所示:

      public class SafeStringEnumConverter : StringEnumConverter
      {
          public object DefaultValue { get; }
      
          public SafeStringEnumConverter(object defaultValue)
          {
              DefaultValue = defaultValue;
          }
      
          public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
          {
              try
              {
                  return base.ReadJson(reader, objectType, existingValue, serializer);
              }
              catch
              {      
                  return DefaultValue;
              }
          }
      }
      

      那么你可以如下使用它:

      [JsonConverter(typeof(SafeStringEnumConverter), Unknown)]
      public enum Colors
      {
          Unknown,
      
          [EnumMember(Value = "MY_VALUE_1")]
          MyValue,
      
          [EnumMember(Value = "MY_VALUE_2")]
          MyValue2
      }
      

      【讨论】:

      • 我非常喜欢这个解决方案!
      【解决方案5】:

      这是Vignesh Chandramohan 答案的一些示例代码。如果您只是反序列化,当然是最简单的解决方案。

      public class SampleClass
      {
          [JsonProperty("sampleEnum")] public string sampleEnumString;
      
          [JsonIgnore]
          public SampleEnum sampleEnum
          {
              get
              {
                  if (Enum.TryParse<SampleEnum>(sampleEnumString, true, out var result))
                  {
                      return result;
                  }
      
                  return SampleEnum.UNKNOWN;
              }
          }
      }
      
      public enum SampleEnum
      {
          UNKNOWN,
          V1,
          V2,
          V3
      }
      

      【讨论】:

      • 仅供参考,如果您不想在SampleClass 的公共界面中公开sampleEnumString 成员private。它仍然可以工作,因为[JsonProperty] 允许反序列化器看到它。
      【解决方案6】:

      改进@BrianRogers 我编写了以下代码,它通过了他的所有测试 + 它处理了 EnumAttribute 问题! (我最近遇到了同样的 Nullables Enums 问题)

      class TolerantEnumConverter : StringEnumConverter
      {
      
          public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
          {
              try
              {
                  return base.ReadJson(reader, objectType, existingValue, serializer);
              }
              catch
              {
                  if (IsNullableType(objectType))
                      return null;
      
                  //I would throw the exception, but to pass the tests 
                  return Enum.Parse(objectType, Enum.GetNames(objectType).First());
              }
          }
      
          private static bool IsNullableType(Type t)
          {
              if (t == null)
                  throw new ArgumentNullException(nameof(t));
      
              return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-04-02
        • 1970-01-01
        • 1970-01-01
        • 2019-07-29
        • 2019-09-16
        • 2017-11-15
        • 1970-01-01
        相关资源
        最近更新 更多