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