Usando las capacidades del serializador System.Text.Json en .NET Core, ¿cómo puedo especificar un valor personalizado para un valor de enumeración, similar a JsonPropertyName ? Por ejemplo:
public enum Example { Trick, Treat, [JsonPropertyName("Trick-Or-Treat")] // Error: Attribute 'JsonPropertyName' is not valid on this declaration type. It is only valid on 'property, indexer' declarations. TrickOrTreat }En .net-core-5.0 y asp.net-core-5.0 , Microsoft ha agregado soporte para deserializar enumeraciones a través de JsonStringEnumConverter Class .
Decora los valores de tu enumeración así:
using System.Runtime.Serialization; public enum VipStatus { [EnumMember(Value = @"IS_VIP")] VIP = 1, [EnumMember(Value = @"IS_NOT_VIP")] NonVIP = 2, }Dada una clase como esta:
class MyClass { public VipStatus MyVipStatus { get; set; } }Podría usar JsonStringEnumConverter en línea para serializar una instancia de la clase como esta:
using System.Text.Json; using System.Text.Json.Serialization; // ... var myObjectWithEnums = new MyClass() { MyVipStatus = VipStatus.NonVIP }; var options = new JsonSerializerOptions(); // Configures serialization to allow strings to be accepted and auto-converted to enum values. options.Converters.Add(new JsonStringEnumConverter()); var json = JsonSerializer.Serialize(myObjectWithEnums, options); // serialized output is: { "myVipStatus": "IS_NOT_VIP"}Si usa ASP.NET Core 5, puede configurar la aplicación al inicio para usar JsonStringEnumConverter para serializar todas las solicitudes entrantes:
public async void ConfigureServices(IServiceCollection services) { // ... services .AddControllers() .AddJsonOptions(options => { // Configures serialization to allow strings to be accepted and auto-converted to enum values. options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); } // ... });Más lecturas: Cómo serializar y deserializar (marshal y unmarshal) JSON en .NET Core . Si está trabajando en ASP.NET, esto también es de su interés: Valores predeterminados web para JsonSerializerOptions .
En el caso de .NET 5:
services.AddControllers() .AddJsonOptions(opts => opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));Actualmente, esto no se admite de fábrica en .net-core-3.0 . Actualmente hay un problema abierto de soporte para EnumMemberAttribute en JsonConverterEnum #31081 que solicita esta funcionalidad. Mientras tanto, deberá crear su propia JsonConverterFactory que serialice enumeraciones con nombres de valores personalizados especificados por atributos.
Si necesita realizar una enumeración de ida y vuelta con nombres de valores personalizados , deberá crear un convertidor genérico + una fábrica de convertidores desde cero. Esto es algo complicado en general, ya que es necesario manejar el análisis de valores enteros y de cadena, cambiar el nombre de cada componente de un valor de enumeración [Flags] y enumeraciones de todos los tipos subyacentes posibles ( byte , short , int , long , ulong , etc.) .
JsonStringEnumMemberConverter de Macross.Json.Extensions parece proporcionar esta funcionalidad cuando la enumeración está decorada con [EnumMember(Value = "custom name")] ; instale el paquete Macross.Json.Extensions y luego haga:
[JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumMemberConverter))] // This custom converter was placed in a system namespace. public enum Example { Trick, Treat, [EnumMember(Value = "Trick-Or-Treat")] TrickOrTreat, }Consulte los documentos aquí para obtener detalles de uso.
Alternativamente, puede crear el suyo propio utilizando StringEnumConverter de StringEnumConverter como modelo de referencia.
Si solo necesita serializar una enumeración con nombres de valores personalizados, puede hacerlo más fácilmente creando una JsonConverterFactory que adapte JsonStringEnumConverter mediante la construcción de una JsonNamingPolicy personalizada para cada tipo de enum que busque la presencia de [EnumMember(Value = "xxx")] atributos en los miembros de la enumeración y, si se encuentra alguno, asigna el nombre del miembro al valor del atributo. (Elegí EnumMember porque este es el atributo admitido por Newtonsoft).
Primero, introduzca el siguiente convertidor:
public class CustomJsonStringEnumConverter : JsonConverterFactory { private readonly JsonNamingPolicy namingPolicy; private readonly bool allowIntegerValues; private readonly JsonStringEnumConverter baseConverter; public CustomJsonStringEnumConverter() : this(null, true) { } public CustomJsonStringEnumConverter(JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true) { this.namingPolicy = namingPolicy; this.allowIntegerValues = allowIntegerValues; this.baseConverter = new JsonStringEnumConverter(namingPolicy, allowIntegerValues); } public override bool CanConvert(Type typeToConvert) => baseConverter.CanConvert(typeToConvert); public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { var query = from field in typeToConvert.GetFields(BindingFlags.Public | BindingFlags.Static) let attr = field.GetCustomAttribute<EnumMemberAttribute>() where attr != null select (field.Name, attr.Value); var dictionary = query.ToDictionary(p => p.Item1, p => p.Item2); if (dictionary.Count > 0) { return new JsonStringEnumConverter(new DictionaryLookupNamingPolicy(dictionary, namingPolicy), allowIntegerValues).CreateConverter(typeToConvert, options); } else { return baseConverter.CreateConverter(typeToConvert, options); } } } public class JsonNamingPolicyDecorator : JsonNamingPolicy { readonly JsonNamingPolicy underlyingNamingPolicy; public JsonNamingPolicyDecorator(JsonNamingPolicy underlyingNamingPolicy) => this.underlyingNamingPolicy = underlyingNamingPolicy; public override string ConvertName (string name) => underlyingNamingPolicy == null ? name : underlyingNamingPolicy.ConvertName(name); } internal class DictionaryLookupNamingPolicy : JsonNamingPolicyDecorator { readonly Dictionary<string, string> dictionary; public DictionaryLookupNamingPolicy(Dictionary<string, string> dictionary, JsonNamingPolicy underlyingNamingPolicy) : base(underlyingNamingPolicy) => this.dictionary = dictionary ?? throw new ArgumentNullException(); public override string ConvertName (string name) => dictionary.TryGetValue(name, out var value) ? value : base.ConvertName(name); } Luego decora tu enum :
public enum Example { Trick, Treat, [EnumMember(Value = "Trick-Or-Treat")] TrickOrTreat, }Y use el convertidor independiente de la siguiente manera:
var options = new JsonSerializerOptions { Converters = { new CustomJsonStringEnumConverter() }, WriteIndented = true, }; var json = JsonSerializer.Serialize(values, options);Para registrar el convertidor con asp.net core, vea, por ejemplo, esta respuesta al equivalente de JsonConverter en el uso de System.Text.Json por Mani Gandham .
Notas:
Este enfoque solo funciona para la serialización porque JsonConverterFactory ignora su política de nombres durante la deserialización; consulte System.Text.Json: JsonStringEnumConverter ignora su JsonNamingPolicy durante la deserialización. #31619 para más detalles.
En .Net Core 3.x, es posible que el convertidor no funcione como se desea con enumeraciones [Flags] como:
[Flags] public enum Example { Trick = (1<<0), Treat = (1<<1), [EnumMember(Value = "Trick-Or-Treat")] TrickOrTreat = (1<<2), } Los valores simples como Example.TrickOrTreat se renombran correctamente, pero los valores compuestos como Example.Trick | Example.TrickOrTreat no lo son. El resultado de este último debería ser "Trick, Trick-Or-Treat" , pero en su lugar es "Trick, TrickOrTreat" .
La causa del problema es que el JsonConverterEnum<T> subyacente para cada tipo de enumeración T específico llama a ConvertName una vez con el nombre compuesto construido en lugar de varias veces con cada componente del nombre compuesto. Si se requiere una solución alternativa, en DictionaryLookupNamingPolicy.ConvertName() puede intentar dividir el nombre entrante en componentes separados por comas, reasignar cada componente y luego volver a combinar los resultados.
A modo de comparación, StringEnumConverter de StringEnumConverter llama al método equivalente NamingStrategy.ResolvePropertyName(string name) en cada componente de un valor de indicador compuesto, lo que parece más correcto.
En .Net 5 esto está arreglado, vea el Problema #31622 para más detalles.
Violín de demostración aquí .