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

As far I have tried, I have 2 solutions, one using System.Text.Json and the other one is Newtonsoft.

System.Text.Json

Your create a custom class using JsonConverter

You introduce Unknown enum in Foo.

in stead of using JsonStringEnumConverter

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

Use your customized class CustomEnumConverter

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

So lets put thing together:

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

Here is the code 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<>));
    }
}

Running this code should return True with out exception.

For this solution I got some inspiration from here.

The other way is a bit similar but using Newtonsoft.

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

Newtonsoft (Original Answer)

Another way to solve this using Newtonsoft with custom JsonConverter.

What you do is added attribute of your custom JsonConverter to your Foo class [JsonConverter(typeof(CustomEnumConverter))].

Then make your class method to return null if the enum is not recognized.

You can of course customize almost any type and have different customization classes.

Ok install Newtonsoft.Json nuget package via Nuget Manager.

We start with you code modification:

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

And now for your customization 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<>));
    }
}

Now it is test time.

When we fire the program with out [JsonConverter(typeof(CustomEnumConverter))] we get error as shown here: enter image description here

But when we added [JsonConverter(typeof(CustomEnumConverter))] and run the program again it works: enter image description here

Links:

  • https://www.newtonsoft.com/json
  • I got inspiration from this answer:
    How can I ignore unknown enum values during json deserialization?
  • https://bytefish.de/blog/enums_json_net/