Skip to content

Instantly share code, notes, and snippets.

@eiriktsarpalis
Last active July 14, 2024 10:26
Show Gist options
  • Save eiriktsarpalis/2c11d8dde598eab1b54281bc67a3df41 to your computer and use it in GitHub Desktop.
Save eiriktsarpalis/2c11d8dde598eab1b54281bc67a3df41 to your computer and use it in GitHub Desktop.
Adding `EnumMemberAttribute` support to System.Text.Json
namespace System.Text.Json.Serialization.DataContractExtensions;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
/// <summary>
/// Provides an AOT-compatible extension for <see cref="JsonStringEnumConverter"/> that adds support for <see cref="EnumMemberAttribute"/>.
/// </summary>
/// <typeparam name="TEnum">The type of the <see cref="TEnum"/>.</typeparam>
public sealed class EnumMemberJsonStringEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>
: JsonStringEnumConverter<TEnum> where TEnum : struct, Enum
{
public EnumMemberJsonStringEnumConverter() : base(namingPolicy: ResolveNamingPolicy())
{
}
private static JsonNamingPolicy? ResolveNamingPolicy()
{
var map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
.Select(f => (f.Name, AttributeName: f.GetCustomAttribute<EnumMemberAttribute>()?.Value))
.Where(pair => pair.AttributeName != null)
.ToDictionary();
return map.Count > 0 ? new EnumMemberNamingPolicy(map!) : null;
}
private sealed class EnumMemberNamingPolicy(Dictionary<string, string> map) : JsonNamingPolicy
{
public override string ConvertName(string name)
=> map.TryGetValue(name, out string? newName) ? newName : name;
}
}
/// <summary>
/// Provides a non-generic variant of <see cref="EnumMemberJsonStringEnumConverter"/> that is not compatible with Native AOT.
/// </summary>
/// <typeparam name="TEnum">The type of the <see cref="TEnum"/>.</typeparam>
[RequiresUnreferencedCode("EnumMemberAttribute annotations might get trimmed.")]
[RequiresDynamicCode("Requires dynamic code generation.")]
public sealed class EnumMemberJsonStringEnumConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type typedFactory = typeof(EnumMemberJsonStringEnumConverter<>).MakeGenericType(typeToConvert);
var innerFactory = (JsonConverterFactory)Activator.CreateInstance(typedFactory)!;
return innerFactory.CreateConverter(typeToConvert, options);
}
}
MyPoco? result = JsonSerializer.Deserialize("""{"Prop1":"A", "Prop2":"B"}""", MyContext.Default.MyPoco);
Console.WriteLine(result!.Prop1); // Value1
Console.WriteLine(result!.Prop2); // Value2
public enum MyEnum
{
[EnumMember(Value = "A")]
Value1 = 1,
[EnumMember(Value = "B")]
Value2 = 2,
}
public class MyPoco
{
[JsonConverter(typeof(EnumMemberJsonStringEnumConverter<MyEnum>))]
public MyEnum Prop1 { get; set; }
[JsonConverter(typeof(EnumMemberJsonStringEnumConverter))]
public MyEnum Prop2 { get; set; }
}
[JsonSerializable(typeof(MyPoco))]
public partial class MyContext : JsonSerializerContext;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment