Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does JsonStringEnumConverter (System.Text.Json) support null values?

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!

like image 929
Svek Avatar asked Dec 17 '19 18:12

Svek


People also ask

Which is better Newtonsoft JSON or System text JSON?

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.

Can JSON serialize null?

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.

Is System text JSON faster than Newtonsoft?

Text. Json is much faster than the Newtonsoft. Json.

Is Newtonsoft JSON still supported?

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.


2 Answers

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.

like image 34
Koen Elsdijk Avatar answered Sep 19 '22 01:09

Koen Elsdijk


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).


The solution. Use a custom converter.

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!

like image 122
Svek Avatar answered Sep 20 '22 01:09

Svek