C# 在json反序列化过程中,如何忽略未知的枚举值?

C# 在json反序列化过程中,如何忽略未知的枚举值?,c#,json.net,C#,Json.net,当我的枚举与Json属性中提供的字符串值不匹配时,如何使Json.net不抛出 当我基于当前文档创建枚举时,就会发生这种情况,但第三方API稍后会添加更多枚举值 我很乐意将特殊值标记为未知值或使用可为null的枚举,不匹配的值将返回null。您可以使用自定义的JsonConverter解决此问题。下面是我使用来自Json.Net的StringEnumConverter类中的一些片段组合而成的一个示例。它应该能让你灵活地处理你决定的任何事情。下面是它的工作原理: 如果在JSON中找到的值与枚举匹

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

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


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

您可以使用自定义的
JsonConverter
解决此问题。下面是我使用来自Json.Net的
StringEnumConverter
类中的一些片段组合而成的一个示例。它应该能让你灵活地处理你决定的任何事情。下面是它的工作原理:

  • 如果在JSON中找到的值与枚举匹配(作为字符串或整数),则使用该值。(如果该值为整数且存在多个可能的匹配项,则使用其中的第一个。)
  • 否则,如果枚举类型可为null,则该值将设置为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
解决此问题。下面是我使用来自Json.Net的
StringEnumConverter
类中的一些片段组合而成的一个示例。它应该能让你灵活地处理你决定的任何事情。下面是它的工作原理:

  • 如果在JSON中找到的值与枚举匹配(作为字符串或整数),则使用该值。(如果该值为整数且存在多个可能的匹配项,则使用其中的第一个。)
  • 否则,如果枚举类型可为null,则该值将设置为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<>));
    }
}

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

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

查看针对此问题存在的少量建议,所有建议都使用StringEnumConverter作为主干,但没有建议通过继承使用它。如果您的场景与我的场景类似,我将使用第三方API响应,该响应包含大量可能的枚举值,这些值可能会随着时间的推移而变化。我只关心其中的10个值,所以我希望所有其他值都返回默认值(如Unknown)。下面是我的枚举转换器,用于执行此操作:

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

通过查看针对此问题的少量建议,所有建议都使用StringEnumConverter作为主干,但没有建议通过继承使用它。如果您的场景与我的场景类似,我将使用第三方API响应,该响应包含大量可能的枚举值,这些值可能会随着时间的推移而变化。我只关心其中的10个值,所以我希望所有其他值都返回默认值(如Unknown)。下面是我的枚举转换器,用于执行此操作:

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

下面是一些示例代码。当然,如果你只是反序列化,这是最简单的解决方案

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
{
[JsonProperty(“sampleEnum”)]公共字符串sampleEnumString;
[JsonIgnore]
公共样本数样本数
{
得到
{
if(Enum.TryParse(sampleEnumString,true,out-var-result))
{
返回结果;
}
返回SampleEnum.UNKNOWN;
}
}
}
公共枚举样本枚举
{
不详,
V1,
V2,
V3
}

以下是一些示例代码。当然,如果你只是反序列化,这是最简单的解决方案

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
{
[JsonProperty(“sampleEnum”)]公共字符串sampleEnumString;
[JsonIgnore]
公共样本数样本数
{
得到
{
if(Enum.TryParse(sampleEnumString,true,out-var-result))
{
返回结果;
}
返回SampleEnum.UNKNOWN;
}
}
}
公共枚举样本枚举
{
不详,
V1,
V2,
V3
}

您可以使用自定义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
}

您可以使用自定义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
}
改进@I编写了以下代码,它通过了他的所有测试+它处理属性问题! (我最近遇到了相同的Nullables枚举问题)

类TolerantEnumConverter:StringEnumConverter
{
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
尝试
{
return base.ReadJson(reader、objectType、existingValue、serializer);
}
抓住
{
if(IsNullableType(objectType))
返回null;
//我会抛出异常,但要通过测试
返回Enum.Parse(objectType,Enum.GetNames(objectType.First());
}
}
私有静态bool IsNullableType(类型t)
{
如果(t==null)
抛出新的ArgumentNullException(nameof(t));
返回(t.IsGenericType&&t.GetGenericTypeDefinition()==typeof(可空));
}
}
改进@我已经编写了以下代码,它通过了他的所有测试+它处理了属性问题! (我最近遇到了相同的Nullables枚举问题)

class-TolerantEnumConverter