8

我的一些 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 preview 8。

4

2 回答 2

8

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

System.Text.Json

您使用创建自定义类JsonConverter

你在 Foo 中引入了 Unknown 枚举。

而不是使用JsonStringEnumConverter

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

使用您的自定义课程CustomEnumConverter

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

运行此代码应返回 True 而不会出现异常。

对于这个解决方案,我从这里得到了一些灵​​感。

另一种方式有点相似,但使用的是 Newtonsoft。

注意:请记住,我在这里所做的只是演示内容的示例,请验证每件事,在投入生产之前对其进行测试。

牛顿软件(原始答案)

Newtonsoft使用custom解决此问题的另一种方法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);
    }
}

现在为您的自定义课程:

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

现在是测试时间。

当我们在没有 out 的情况下启动程序时,[JsonConverter(typeof(CustomEnumConverter))]会出现如下所示的错误: 在此处输入图像描述

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

链接:

于 2019-09-21T22:23:26.770 回答
1

您可以反序列化为字符串并 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);
于 2019-09-19T19:17:37.063 回答