Search code examples
c#jsonjson-deserializationsystem.text.json

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


I have an enum:

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

It is given in the json by a number. Eg:

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

It will successfully deserialize but I want to throw an exception if the value is NOT (0 OR 5 OR 13 OR 15).

Is that possible to do via an attribute using System.Text.Json?


Solution

  • Improvement on @MestreDosMagros's answer:

    I created a [CheckedEnum] attribute.

    It will throw an exception if the value is not in the enum.

    You will need a different algorithm for a [Flags] enum.

    Example:
    public class OrderInfo
    {
        public TaxType TaxType { get; set; }
    }
    
    [CheckedEnum]
    public enum TaxType : byte
    {
        None = 0,
        GSTCanada = 5,
        HSTOntario = 13,
        HSTOther = 15
    }
    

    (.NET fiddle tests)

    Code:

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