0

I've troubles deserializing JSON values to specific enum values with .NET6 standard JSON serializer. I hoped that EnumMember attribute will work, but it doesn't.

Here's some sample code:

using System;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
                    
public class Program
{
    private enum Status {
        Open,
        Closed,
        
        [EnumMember(Value = "NOT_AVAILABLE")]
        NotAvailable
    }
        
    private class MyJson {
    
        [JsonPropertyName("user_name")]
        public string UserName {get; set;} = string.Empty;
        
        [JsonConverter(typeof(JsonStringEnumConverter))]
        [JsonPropertyName("status")]
        public Status? Status {get; set;}
        
        public override string ToString() => $"{{UserName: {UserName}, Status: {Status}}}";
    }
    
    public static void Main()
    {
        string input;
        Object obj;
        
        input = @"{ ""user_name"": ""jdoe"", ""status"": ""CLOSED"" }";
        obj = JsonSerializer.Deserialize<MyJson>(input);
        Console.WriteLine($"Hello World, {obj}");
        
        Console.WriteLine("");
        
        input = @"{ ""user_name"": ""jdoe"", ""status"": ""NOT_AVAILABLE"" }";
        obj = JsonSerializer.Deserialize<MyJson>(input);
        Console.WriteLine($"Hello World, {obj}");
        
    }
}

The output is:

Hello World, {UserName: jdoe, Status: Closed}

Unhandled exception. System.Text.Json.JsonException: The JSON value could not be converted to System.Nullable`1[Program+Status]. Path: $.status | LineNumber: 0 | BytePositionInLine: 48.
   at System.Text.Json.ThrowHelper.ThrowJsonException(String message)
   at System.Text.Json.Serialization.Converters.EnumConverter`1.ReadAsPropertyNameCore(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Converters.EnumConverter`1.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Converters.NullableConverter`1.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Program.Main()
Command terminated by signal 6

Here's the example as .NET Fiddle

Is there any way to achieve this without an external library?

Edit:

Answered in linked question, but I provide my solution here as well as it is more concise than the one in the linked thread and based on the linked github dotnet/runtime issue:

Create a converter class:

public class JsonStringEnumMemberConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, System.Enum
{

    private readonly Dictionary<TEnum, string> _enumToString = new();
    private readonly Dictionary<string, TEnum> _stringToEnum = new();
    private readonly Dictionary<int, TEnum> _numberToEnum = new();

    public JsonStringEnumMemberConverter()
    {
        var type = typeof(TEnum);

        foreach (var value in Enum.GetValues<TEnum>())
        {
            var enumMember = type.GetMember(value.ToString())[0];
            var attr = enumMember.GetCustomAttributes(typeof(EnumMemberAttribute), false)
              .Cast<EnumMemberAttribute>()
              .FirstOrDefault();

            _stringToEnum.Add(value.ToString(), value);
            var num = Convert.ToInt32(type.GetField("value__")?
                    .GetValue(value));
            if (attr?.Value != null)
            {
                _enumToString.Add(value, attr.Value);
                _stringToEnum.Add(attr.Value, value);
                _numberToEnum.Add(num, value);
            }
            else
            {
                _enumToString.Add(value, value.ToString());
            }
        }
    }

    public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var type = reader.TokenType;
        if (type == JsonTokenType.String)
        {
            var stringValue = reader.GetString() ?? "";
            if (_stringToEnum.TryGetValue(stringValue, out var enumValue))
            {
                return enumValue;
            }
        }
        else if (type == JsonTokenType.Number)
        {
            var numValue = reader.GetInt32();
            _numberToEnum.TryGetValue(numValue, out var enumValue);
            return enumValue;
        }

        return default;
    }

    public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(_enumToString[value]);
    }
}

Use converter for specific enum:

private enum Status {
    Open,
    Closed,
    
    [EnumMember(Value = "NOT_AVAILABLE")]
    NotAvailable
}
    
private class MyJson {

    [JsonPropertyName("user_name")]
    public string UserName {get; set;} = string.Empty;
    
    [JsonConverter(typeof(JsonStringEnumMemberConverter<Status>))]
    [JsonPropertyName("status")]
    public Status? Status {get; set;}
    
    public override string ToString() => $"{{UserName: {UserName}, Status: {Status}}}";
}
grasbueschel
  • 879
  • 2
  • 8
  • 24
  • @JeroenMostert thank you!! yes, that thread contains a working example using a custom `JsonConverter` that works for me! – grasbueschel Dec 03 '22 at 11:16
  • The converter in the linked thread is intended to handle `[Flags]` enums, as well as non-integer enums. If you don't need that, the converter can be simpler. – dbc Dec 03 '22 at 16:27

0 Answers0