是否有 System.Text.Json 属性只允许有效的枚举值?

Is there a System.Text.Json attribute to only allow valid enum values?

我有一个枚举:

public enum TaxType : byte
{
    None = 0,
    GSTCanada = 5,
    HSTOntario = 13,
    HSTOther = 15
}

它在 json 中由一个数字给出。例如:

{"TaxType": 13, ...}
public class OrderInfo
{
    public TaxType TaxType { get; set; }
    /// ...
}

它将成功反序列化,但如果值为 NOT (0 OR 5 OR 13 OR 15),我想抛出异常。

是否可以使用 System.Text.Json 通过属性来做到这一点?

您可以为枚举创建自定义转换器,如下所示:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

var jsonOpt = new JsonSerializerOptions();
jsonOpt.Converters.Add(new CustomEnumConverter());
Console.WriteLine(JsonSerializer.Deserialize<OrderInfo>("{\"TaxType\":14}", jsonOpt).TaxType);

public enum TaxType
{
    None = 0,
    GSTCanada = 5,
    HSTOntario = 13,
    HSTOther = 15
}

public class OrderInfo
{
    public TaxType TaxType { get; set; }
}

public class CustomEnumConverter : JsonConverter<TaxType>
{
    public override TaxType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var value = reader.GetInt32();
        if (Enum.IsDefined(typeToConvert, value))
        {
            return (TaxType) value;
        }
        throw new Exception("Value is invalid");
    }

    public override void Write(Utf8JsonWriter writer, TaxType value, JsonSerializerOptions options)
    {
        writer.WriteNumber("taxType", (decimal)(int)value);
    }
}

显然这并不完全通用,您需要做一些工作,但您明白了。

改进@MestreDosMagros的回答:

我创建了一个 [CheckedEnum] 属性。

如果值不在枚举中,将抛出异常。

[Flags] 枚举需要不同的算法。

示例:
public class OrderInfo
{
    public TaxType TaxType { get; set; }
}

[CheckedEnum]
public enum TaxType : byte
{
    None = 0,
    GSTCanada = 5,
    HSTOntario = 13,
    HSTOther = 15
}

(.NET fiddle tests)

代码:

public class CheckedEnumAttribute : JsonConverterAttribute { public CheckedEnumAttribute() : base(typeof(CheckedEnumConverterFactory)) { } }

public class CheckedEnumConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
        => typeToConvert.IsEnum;

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        => (JsonConverter)Activator.CreateInstance(typeof(CheckedEnumConverter<>).MakeGenericType(typeToConvert));
}

public class CheckedEnumConverter<T> : JsonConverter<T> where T: struct, Enum
{
    static readonly TypeCode typeCode = Type.GetTypeCode(typeof(T));

    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        T value = (T)(object)(typeCode switch
        {
            TypeCode.SByte => reader.GetSByte(),
            TypeCode.Byte => reader.GetByte(),
            TypeCode.Int16 => reader.GetInt16(),
            TypeCode.UInt16 => reader.GetUInt16(),
            TypeCode.Int32 => reader.GetInt32(),
            TypeCode.UInt32 => reader.GetUInt32(),
            TypeCode.Int64 => reader.GetInt64(),
            TypeCode.UInt64 => reader.GetUInt64()
        });
        if (!Enum.IsDefined(value)) throw new Exception($"Value {value} is invalid!");
        return value;
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        if (!Enum.IsDefined(value)) throw new Exception($"Value {value} is invalid!");
        if (typeCode == TypeCode.UInt64)
            writer.WriteNumberValue((ulong)(object)value);
        else
            writer.WriteNumberValue(Convert.ToInt64(value));
    }
}