I am shifting my code from .NET Core 2.x to .NET Core 3.x (i.e. use the native library System.Text.Json). In doing this, I ran into some issues with how the former Newtonsoft.Json support for nullable enums does not have a clear migration path at the moment --- it looks like it is not supported in .NET Core 3.x?.
For example, using Newtonsoft.Json, the JSON converter supported nullable enums, like so:
public enum UserStatus
{
    NotConfirmed,
    Active,
    Deleted
}
public class User
{
    public string UserName { get; set; }
    [JsonConverter(typeof(StringEnumConverter))]  // using Newtonsoft.Json
    public UserStatus? Status { get; set; }       // Nullable Enum
}
The current version of the native library
System.Text.Json, does not seem to support this.
How do I solve this problem? I cannot migrate my code!
Json does case-insensitive property name matching by default. The System. Text. Json default is case-sensitive, which gives better performance since it's doing an exact match.
While the implementation itself was a breeze, unfortunately this still didnt work - CanConvert() is never called for a property that has a null value, and therefore WriteJson() is not called either. Apparently nulls are automatically serialized directly into null , without the custom pipeline.
Text. Json is much faster than the Newtonsoft. Json.
Json was basically scrapped by Microsoft with the coming of . NET Core 3.0 in favor of its newer offering designed for better performance, System.
I found Svek's answer very helpful, however I wanted to have the converter compatible with nullable as well as non-nullable enum properties.
I accomplished this by tweaking his converter as follows:
public class JsonNullableEnumStringConverter<TEnum> : JsonConverter<TEnum>
{
    private readonly bool _isNullable;
    private readonly Type _enumType;
    public JsonNullableEnumStringConverter() {
        _isNullable = Nullable.GetUnderlyingType(typeof(TEnum)) != null;
        // cache the underlying type
        _enumType = _isNullable ? 
            Nullable.GetUnderlyingType(typeof(TEnum)) : 
            typeof(TEnum);
    }
    public override TEnum Read(ref Utf8JsonReader reader,
        Type typeToConvert, JsonSerializerOptions options)
    {
        var value = reader.GetString();
        if (_isNullable && string.IsNullOrEmpty(value))
            return default; //It's a nullable enum, so this returns null. 
        else if (string.IsNullOrEmpty(value))
            throw new InvalidEnumArgumentException(
                $"A value must be provided for non-nullable enum property of type {typeof(TEnum).FullName}");
        // for performance, parse with ignoreCase:false first.
        if (!Enum.TryParse(_enumType, value, false, out var result)
            && !Enum.TryParse(_enumType, value, true, out result))
        {
            throw new JsonException(
                $"Unable to convert \"{value}\" to Enum \"{_enumType}\".");
        }
        return (TEnum)result;
    }
    public override void Write(Utf8JsonWriter writer,
        TEnum value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value?.ToString());
    }
}
I also left out some elements that I didn't need in my solution. Hope this is helpful to someone out there.
Unfortunately, there is currently no support "out-of-the-box" in System.Text.Json to convert nullable enums.
However, there is a solution by using your own custom converter. (see below).
You would attach can attach it to your property by decorating it with the custom converter:
// using System.Text.Json
[JsonConverter(typeof(StringNullableEnumConverter<UserStatus?>))]  // Note the '?'
public UserStatus? Status { get; set; }                            // Nullable Enum
Here is the converter:
public class StringNullableEnumConverter<T> : JsonConverter<T>
{
    private readonly JsonConverter<T> _converter;
    private readonly Type _underlyingType;
    public StringNullableEnumConverter() : this(null) { }
    public StringNullableEnumConverter(JsonSerializerOptions options)
    {
        // for performance, use the existing converter if available
        if (options != null)
        {
            _converter = (JsonConverter<T>)options.GetConverter(typeof(T));
        }
        // cache the underlying type
        _underlyingType = Nullable.GetUnderlyingType(typeof(T));
    }
    public override bool CanConvert(Type typeToConvert)
    {
        return typeof(T).IsAssignableFrom(typeToConvert);
    }
    public override T Read(ref Utf8JsonReader reader, 
        Type typeToConvert, JsonSerializerOptions options)
    {
        if (_converter != null)
        {
            return _converter.Read(ref reader, _underlyingType, options);
        }
        string value = reader.GetString();
        if (String.IsNullOrEmpty(value)) return default;
        // for performance, parse with ignoreCase:false first.
        if (!Enum.TryParse(_underlyingType, value, 
            ignoreCase: false, out object result) 
        && !Enum.TryParse(_underlyingType, value, 
            ignoreCase: true, out result))
        {
            throw new JsonException(
                $"Unable to convert \"{value}\" to Enum \"{_underlyingType}\".");
        }
        return (T)result;
    }
    public override void Write(Utf8JsonWriter writer, 
        T value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value?.ToString());
    }
}
Hope that helps until there is native support without the need for a custom converter!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With