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

时间:2014-03-31 01:03:39

标签: c# json.net

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

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

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

6 个答案:

答案 0 :(得分:37)

您可以使用自定义JsonConverter解决此问题。这是我使用来自Json.Net的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<>));
    }
}

这是一个演示,它使用几个不同的枚举(一个有&#34;未知&#34;值,另一个没有)来使转换器完成其步调:

[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)

答案 1 :(得分:12)

仔细研究针对此问题存在的一些建议,所有建议都使用StringEnumConverter作为主干,但没有建议通过继承使用它。如果您的情况像我的情况一样,那么我正在执行第三方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
}

答案 2 :(得分:7)

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

答案 3 :(得分:4)

您可以使用自定义的StringEnumConverter。

public class SafeStringEnumConverter : StringEnumConverter
{
    public object DefaultValue { get; }

    public SafeStringEnumConverter(object defaultValue)
    {
        DefalutValue = 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
}

答案 4 :(得分:0)

以下是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
}

答案 5 :(得分:0)

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

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<>));
    }
}