通过字符串转换反序列化枚举时,如何获得空值而不是序列化错误?

How can I get a null value instead of a serialization error when deserializing an enum by string conversion?

我的一些 API 端点具有包含枚举的模型。 FluentValidation 用于验证发送的值是否满足各自的要求。

为了提高可用性和文档生成,允许将枚举作为字符串而不是整数发送。如果发送无效整数,验证发送的值是否在正确范围内工作正常,但如果发送无效字符串,序列化将失败。

public enum Foo 
{
    A = 1,
    B = 2
}

public class Bar 
{
    public Foo? Foo {get;set;}
}

void Main()
{
    var options = new JsonSerializerOptions();
    options.Converters.Add(new JsonStringEnumConverter());
    options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

    var jsonString = "{\"foo\": \"C\"}";
    var jsonSpan = (ReadOnlySpan<byte>)Encoding.UTF8.GetBytes(jsonString);

    try
    {
        var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
        Console.WriteLine(result.Foo == null);
    }
    catch(Exception ex) 
    {
        Console.WriteLine("Serialization Failed");
    }
}

我想要的结果是当字符串与枚举的任何字段都不匹配时,简单地将枚举 属性 反序列化为 null,以便可以将模型传递给验证器以创建友好消息。

我怎样才能做到这一点?这是使用带有 System.Text.Json API.

的 net-core 3 预览版 8

你可以反序列化成一个字符串然后 TryParse

public class Bar
{
    public string Foo { get; set; }
    public Foo? FooEnum { get; set; }
}

...
var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
Enum.TryParse<Foo>(result, out Bar.FooEnum);

据我所知,我有 2 个解决方案,一个使用 System.Text.Json,另一个是 Newtonsoft

System.Text.Json

您使用 JsonConverter

创建自定义 class

您在 Foo 中引入了未知枚举。

而不是使用 JsonStringEnumConverter

options.Converters.Add(new JsonStringEnumConverter());

使用你自定义的classCustomEnumConverter

options.Converters.Add(new CustomEnumConverter());

所以让我们把事情放在一起:

public enum Foo
{
    A = 1,
    B = 2,
    // what ever name and enum number that fits your logic
    Unknown = 99
}

public class Bar
{
    public Foo? Foo { get; set; }
}   

public static void Main()
{
    var options = new JsonSerializerOptions();
    options.Converters.Add(new CustomEnumConverter());
    options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

    var jsonString = "{\"foo\": \"C\"}";
    var jsonSpan = (ReadOnlySpan<byte>)Encoding.UTF8.GetBytes(jsonString);

    try
    {
        var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);

        if (result.Foo == Foo.Unknown)
            result.Foo = null;

        Console.WriteLine(result.Foo == null);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Serialization Failed" + ex.Message);
    }
}

这里是代码 CustomEnumConverter

internal sealed class CustomEnumConverter : JsonConverter<Foo>
{
    public override Foo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.String:
                var isNullable = IsNullableType(typeToConvert);
                var enumType = isNullable ? Nullable.GetUnderlyingType(typeToConvert) : typeToConvert;
                var names = Enum.GetNames(enumType ?? throw new InvalidOperationException());
                if (reader.TokenType != JsonTokenType.String) return Foo.Unknown;
                var enumText = System.Text.Encoding.UTF8.GetString(reader.ValueSpan);
                if (string.IsNullOrEmpty(enumText)) return Foo.Unknown;
                var match = names.FirstOrDefault(e => string.Equals(e, enumText, StringComparison.OrdinalIgnoreCase));
                return (Foo) (match != null ? Enum.Parse(enumType, match) : Foo.Unknown);
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    public override void Write(Utf8JsonWriter writer, Foo value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }

    private static bool IsNullableType(Type t)
    {
        return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
    }
}

运行 此代码应 return 正确无一例外。

对于此解决方案,我从 here 中获得了一些灵感。

另一种方法有点类似,但使用的是 Newtonsoft。

Note: Remember what I did here is just example to demonstrate stuff, please validate every thing, test it before going production.

Newtonsoft(原答案)

使用 Newtonsoft 和自定义 JsonConverter 解决此问题的另一种方法。

你所做的是将自定义 JsonConverter 的属性添加到你的 Foo class [JsonConverter(typeof(CustomEnumConverter))].

如果 enum 无法识别,则将您的 class 方法设为 return null

你当然可以自定义几乎任何类型,并且有不同的自定义 classes.

好的,通过 Nuget 管理器安装 Newtonsoft.Json nuget 包。

我们先为您修改代码:

//add the attribute here
[JsonConverter(typeof(CustomEnumConverter))]
public enum Foo
{
    A = 1,
    B = 2
}

public class Bar
{
    public Foo? Foo { get; set; }
}

public static void Main()
{
    var jsonString = "{\"foo\": \"C\"}";

    try
    {
        // use newtonsoft json converter
        var result = JsonConvert.DeserializeObject<Bar>(jsonString);
        Console.WriteLine(result.Foo == null);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Serialization Failed" + ex.Message);
    }
}

现在进行自定义 class:

public class CustomEnumConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        var type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType;
        return type != null && type.IsEnum;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var isNullable = IsNullableType(objectType);
        var enumType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;
        var names = Enum.GetNames(enumType ?? throw new InvalidOperationException());

        if (reader.TokenType != JsonToken.String) return null;
        var enumText = reader.Value.ToString();

        if (string.IsNullOrEmpty(enumText)) return null;
        var match = names.FirstOrDefault(e => string.Equals(e, enumText, StringComparison.OrdinalIgnoreCase));

        return match != null ? Enum.Parse(enumType, match) : null;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString());
    }

    public override bool CanWrite => true;

    private static bool IsNullableType(Type t)
    {
        return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
    }
}

现在是测试时间。

当我们在没有 [JsonConverter(typeof(CustomEnumConverter))] 的情况下启动程序时,我们会得到如下所示的错误:

但是当我们再次添加 [JsonConverter(typeof(CustomEnumConverter))] 和 运行 程序时,它就可以工作了:

链接: