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

Jon*_*ase 6 c# .net-core system.text.json

我的一些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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我该如何实现?这是通过System.Text.Json API使用网络核心3预览版8。

may*_*ʎɐɯ 5

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

系统文本

您使用创建一个自定义类 JsonConverter

您在Foo中引入未知枚举。

代替使用 JsonStringEnumConverter

options.Converters.Add(new JsonStringEnumConverter());
Run Code Online (Sandbox Code Playgroud)

使用您的自定义课程 CustomEnumConverter

options.Converters.Add(new CustomEnumConverter());
Run Code Online (Sandbox Code Playgroud)

因此,让我们放在一起:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是代码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<>));
    }
}
Run Code Online (Sandbox Code Playgroud)

运行此代码应返回True且没有异常。

对于此解决方案,我从这里得到了一些启发。

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

注意:请记住,我在这里所做的只是演示内容的示例,请在生产之前验证所有内容并进行测试。

Newtonsoft(原始答案)

解决此问题的另一种方法是使用Newtonsoftcustom JsonConverter

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

然后,null如果enum未识别,则使您的类方法返回。

当然,您几乎可以自定义任何类型,并具有不同的自定义类。

确定通过Nuget Manager安装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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在是您的定制类:

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<>));
    }
}
Run Code Online (Sandbox Code Playgroud)

现在是测试时间。

当我们不执行程序而启动程序时,[JsonConverter(typeof(CustomEnumConverter))]将显示错误,如下所示: 在此处输入图片说明

但是,当我们添加[JsonConverter(typeof(CustomEnumConverter))]并再次运行该程序时,它可以工作: 在此处输入图片说明

链接: