De forma predeterminada, una enumeración de C# se serializa como número. Para obtener su nombre, System.Text.Json necesita un JsonStringEnumConverter y Newtonsoft.Json, un StringEnumConverter; cualquiera de los dos se aplica a una propiedad, a un tipo de enumeración, a una llamada al serializador o a toda la aplicación.
DataContractJsonSerializer es la excepción: no tiene convertidor ni forma alguna de hacerlo, escribe todas las enumeraciones como números e ignora [EnumMember]. En este artículo vemos los tres, desde una sola propiedad decorada hasta una opción global, además de las enumeraciones de marcas y las cadenas personalizadas.
Vamos a verlo.
¿Por qué C# serializa una enumeración como número de forma predeterminada?
Tanto System.Text.Json como Newtonsoft.Json escriben una enumeración como su entero subyacente, salvo que les indiquemos lo contrario. Color.LightGray se convierte en 1, no en "LightGray".
El entero es el valor real de la enumeración. El nombre del miembro está en los metadatos, y llegar a él tiene un costo: el analizador AOT de .NET emite IL3050 sobre JsonStringEnumConverter precisamente porque construir sus convertidores requiere generar código en tiempo de ejecución.
Ese comportamiento predeterminado rompe la interoperabilidad. Un cliente de JavaScript que recibe "backColor": 1 tiene que mantener su propia copia del orden de nuestra enumeración, y cualquier cambio en el orden de los miembros altera sin avisar el significado de los documentos ya almacenados.
Insertar un miembro en medio es el fallo con el que se topa todo el mundo. Si agregas un valor antes de LightGray, cada 1 guardado pasa a significar otra cosa, sin nada en el payload que lo indique.
Serializar el nombre, en cambio, hace que el payload se describa a sí mismo. "backColor": "LightGray" sobrevive a una reordenación, se lee correctamente en un log y no necesita una tabla de constantes compartida en el lado del consumidor.
Si el valor nunca va a acabar en JSON, no necesitamos un serializador para esto. Hay formas más sencillas de obtener un miembro de una enumeración como cadena sin serializador.
Para empezar, vamos a preparar algunos modelos de objetos:
public class Canvas
{
public static Canvas Poster
=> new() { Name = "Poster", BackColor = Color.LightGray, Pen = new ("Simple", Color.Red) };
public string? Name { get; set; }
public Color BackColor { get; set; }
public Medium Medium { get; set; }
public Pen? Pen { get; set; }
}
public record struct Pen(string Name, Color Color);
public enum Color
{
White, LightGray, DarkGray, Red
}
public enum Medium
{
Water, Oil
}
Declaramos dos enumeraciones, Color y Medium, un record Pen y una clase Canvas. Canvas es el modelo principal que nos ocupa. Además, declaramos una propiedad estática de tipo Canvas (Poster) para usarla cómodamente en los ejemplos.
A continuación, vamos a agregar un método Serialize básico a la clase base (UnitTestBase), con las líneas global using al principio de su archivo. Usamos un proyecto de consola para cada biblioteca, y el de Newtonsoft necesita dotnet add package Newtonsoft.Json. Fuera de las secciones de ASP.NET Core, los tipos de los fragmentos de código van a nivel de archivo en estos proyectos, y las instrucciones de cada fragmento van en un método propio, dentro de una clase que deriva de UnitTestBase:
// Native
global using System.Text.Json;
global using System.Text.Json.Serialization;
public static string Serialize(object obj)
{
return JsonSerializer.Serialize(obj);
}
// Newtonsoft
global using Newtonsoft.Json;
global using Newtonsoft.Json.Converters;
global using Newtonsoft.Json.Serialization;
public static string Serialize(object obj)
{
return JsonConvert.SerializeObject(obj);
}
Con esto, ya lo tenemos todo listo.
Primero, vamos a comprobar el comportamiento predeterminado de la serialización con el objeto Canvas.Poster:
var json = Serialize(Canvas.Poster);
Y veamos el resultado:
{
"Name": "Poster",
"BackColor": 1,
"Medium": 0,
"Pen": {
"Name": "Simple",
"Color": 3
}
}
Como era de esperar, la cadena resultante contiene las propiedades de enumeración (BackColor, Medium, Pen.Color) como valores enteros.
Así que surge la pregunta: “¿Se puede serializar una enumeración como cadena en C#?”. Vamos a buscar la respuesta en el resto del artículo.
¿Qué convertidor serializa una enumeración como cadena?
Tres convertidores resuelven casi todos los casos, y elegir uno depende de qué biblioteca se encarga de escribir.
System.Text.Json usa JsonStringEnumConverter, de System.Text.Json.Serialization. Lo registramos en una propiedad, en el tipo de enumeración, en una instancia de JsonSerializerOptions o una sola vez para toda la aplicación.
Newtonsoft.Json usa StringEnumConverter, de Newtonsoft.Json.Converters. Admite las mismas cuatro ubicaciones y acepta una estrategia de nomenclatura.
DataContractJsonSerializer no tiene ningún convertidor que agregar. Escribe todas las enumeraciones como números e ignora [EnumMember], así que ahí la cadena sencillamente no es una opción.
Las dos ubicaciones del atributo son selectivas. En una propiedad, solo cambia esa propiedad; en la declaración de la enumeración, cambian todas las propiedades de ese tipo allí donde aparezcan.
El ámbito importa más que la biblioteca. Un convertidor registrado de forma global no se ve en el modelo, que suele ser lo que queremos, mientras que un [JsonConverter] a nivel de propiedad tiene prioridad sobre uno registrado globalmente, así que el atributo es el más restringido y el que más peso tiene de los dos.
| Biblioteca | Tipo que hay que usar | Atributo para nombres personalizados |
|---|---|---|
System.Text.Json | JsonStringEnumConverter | JsonStringEnumMemberName |
System.Text.Json (generación de código fuente / AOT) | JsonStringEnumConverter<TEnum> | JsonStringEnumMemberName |
Newtonsoft.Json | StringEnumConverter | EnumMember |
DataContractJsonSerializer | Ninguno, no es posible | Ninguno, siempre es un número |
Serialización de una propiedad de enumeración
Antes que nada, queremos serializar la propiedad BackColor como cadena. Así que es el momento de modificar el modelo Canvas y decorar la propiedad BackColor con el atributo del convertidor:
// Native
public class Canvas
{
...
[JsonConverter(typeof(JsonStringEnumConverter))]
public Color BackColor { get; set; }
...
}
// Newtonsoft
public class Canvas
{
...
[JsonConverter(typeof(StringEnumConverter))]
public Color BackColor { get; set; }
...
}
Ahora, la serialización de Canvas.Poster:
var json = Serialize(Canvas.Poster);
Produce una salida distinta:
{
"Name": "Poster",
"BackColor": "LightGray",
"Medium": 0,
"Pen": {
"Name": "Simple",
"Color": 3
}
}
Vemos que BackColor pasa a ser una cadena, pero Medium y Pen.Color no, porque no tienen aplicado el atributo del convertidor. Esto significa que, de esta forma, podemos serializar de manera selectiva propiedades enum concretas.
Serialización de un tipo de enumeración
A continuación, queremos serializar como cadenas todas las instancias de la enumeración Color. Podemos hacerlo aplicando el atributo del convertidor al propio tipo de enumeración en lugar de a las propiedades:
public class Canvas
{
...
public Color BackColor { get; set; }
...
}
// Native
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Color
{
White, LightGray, DarkGray, Red
}
// Newtonsoft
[JsonConverter(typeof(StringEnumConverter))]
public enum Color
{
White, LightGray, DarkGray, Red
}
De nuevo, tras la serialización, el resultado final es distinto del anterior:
{
"Name": "Poster",
"BackColor": "LightGray",
"Medium": 0,
"Pen": {
"Name": "Simple",
"Color": "Red"
}
}
Esta vez vemos que todas las instancias de Color (BackColor y Pen.Color) pasan a ser cadenas, pero la enumeración Medium sigue con el comportamiento predeterminado. De esta forma, podemos serializar como cadena solo determinados tipos de enumeración.

Ebook gratis
¿Tu Web API está lista para producción?
33 puntos que comprobar antes de desplegarla, con la solución de cada uno. Un PDF gratuito de 76 páginas para .NET 10.
El ebook está en inglés.
Descarga la checklist gratisPDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.
¿Cómo serializar como cadenas las enumeraciones de un objeto concreto?
En algunos casos no podemos recurrir a los enfoques basados en atributos, por ejemplo:
- Cuando trabajamos con tipos del sistema o con modelos de bibliotecas de terceros
- Cuando serializamos objetos generados sobre la marcha, p. ej., objetos anónimos
- Cuando no queremos ensuciar los modelos de dominio, pero sí queremos obtener las enumeraciones como cadenas
- Cuando queremos convertir las enumeraciones en cadenas solo para una instancia concreta de un objeto, no para todas las instancias en general
En esos casos, si no nos importa la serialización selectiva, podemos indicarle al serializador que convierta las enumeraciones bajo demanda. Las dos bibliotecas ofrecen una sobrecarga para pasar los convertidores directamente en la llamada al método de serialización. Así que vamos a implementar nuestra segunda rutina de serialización en la clase base:
// Native
public static string SerializeWithStringEnum(object obj)
{
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
return JsonSerializer.Serialize(obj, options);
}
// Newtonsoft
public static string SerializeWithStringEnum(object obj)
{
var converter = new StringEnumConverter();
return JsonConvert.SerializeObject(obj, converter);
}
En la versión nativa, creamos una instancia de la clase JsonSerializerOptions. Después registramos en ella el convertidor de enumeraciones y, por último, llamamos al método Serialize correspondiente.
Con Newtonsoft la cosa es algo más sencilla. Podemos pasar el convertidor directamente al método de serialización.
A continuación, vamos a quitar los atributos del convertidor de todos los modelos y, en su lugar, aplicar este nuevo método a Canvas.Poster y a un objeto anónimo:
var poster = SerializeWithStringEnum(Canvas.Poster);
var schedule = SerializeWithStringEnum(new { Description = "Exhibition", Day = DayOfWeek.Monday });
Ahora podemos ver el resultado:
/* poster */
{
"Name": "Poster",
"BackColor": "LightGray",
"Medium": "Water",
"Pen": {
"Name": "Simple",
"Color": "Red"
}
}
/* schedule */
{
"Description": "Exhibition",
"Day": "Monday"
}
Como esperábamos, todas las enumeraciones de los objetos de destino se serializan como cadenas.
¿Cómo hacer que todas las enumeraciones se serialicen como cadenas?
Los enfoques basados en atributos nos dan flexibilidad para manejar la serialización de las enumeraciones de forma controlada. Y el enfoque basado en opciones nos permite ocuparnos de instancias concretas de objetos bajo demanda. Pero ¿hay alguna forma de que todas las enumeraciones se serialicen como cadenas de forma predeterminada? ¡Sí, la hay!
Para que el convertidor de enumeraciones a cadenas sea la opción predeterminada al serializar enumeraciones, necesitamos algo de configuración de arranque, que varía según el tipo de aplicación. Normalmente, esto significa que tenemos que registrar el convertidor de enumeraciones en el contenedor de inyección de dependencias (DI). Aquí nos centraremos principalmente en las aplicaciones ASP.NET Core. El convertidor de enumeraciones es una de varias cosas que podemos configurar cuando establecemos estas opciones de forma global para toda la aplicación.
Configurar una Web API de ASP.NET Core
Vamos a partir de un proyecto básico de ASP.NET Core Web API y a quitar todos los controladores y modelos generados automáticamente. A continuación, vamos a agregar los modelos de objetos como de costumbre y un CanvasController:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class CanvasController : ControllerBase
{
[HttpGet("poster")]
public Canvas GetPoster() => Canvas.Poster;
[HttpGet("schedule")]
public object GetSchedule() => new { Description = "Exhibition", Day = DayOfWeek.Monday };
}
Es un controlador de Web API típico con dos endpoints sencillos: “canvas/poster” y “canvas/schedule”. Devuelven, respectivamente, Canvas.Poster y un objeto anónimo.
Ahora vamos al punto de entrada, la clase Program:
// Native
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
var app = builder.Build();
app.MapControllers();
app.Run();
La parte resaltada es todo lo que tenemos que cambiar en este código repetitivo (boilerplate). Simplemente usamos el método AddJsonOptions para acceder, a través del contenedor de DI, a la configuración de JsonSerializerOptions. Ahí es donde podemos registrar el convertidor nativo de enumeraciones a cadenas. ¡Y listo! Cada vez que MVC intente serializar una enumeración a JSON, tomará este convertidor de las opciones registradas en el contenedor de DI.
Con Newtonsoft podemos hacer lo mismo de forma similar:
using Newtonsoft.Json.Converters;
...
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});
...
De nuevo, lo único que necesitamos es encontrar el método de configuración de DI adecuado para usar el serializador de Newtonsoft. Para ello, tenemos que instalar el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson, que proporciona el método de configuración AddNewtonsoftJson. Este método expone la configuración del serializador, donde podemos registrar el convertidor de enumeraciones de Newtonsoft.
Ahora podemos examinar la salida de los endpoints de la API desde Postman o directamente desde un navegador:
/* http://localhost:5045/canvas/poster */
{
"name": "Poster",
"backColor": "LightGray",
"medium": "Water",
"pen": {
"name": "Simple",
"color": "Red"
}
}
/* http://localhost:5045/canvas/schedule */
{
"description": "Exhibition",
"day": "Monday"
}
¡Obtenemos exactamente lo que queríamos: todas las enumeraciones como cadenas de forma predeterminada!
Configurar una API mínima de ASP.NET Core
A continuación, vamos a ver la configuración de las API mínimas. En esencia, una API mínima es una Web API, pero los pasos de arranque son algo distintos. En .NET 10, las API mínimas siguen sin tener integración nativa con Newtonsoft.Json: AddNewtonsoftJson() configura MVC, no el pipeline de las API mínimas. Así que solo vamos a tratar la biblioteca System.Text.Json.
En un proyecto nuevo de API mínima con nuestros modelos de objetos, vamos a modificar la clase Program generada automáticamente y a agregar el código de arranque:
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
var app = builder.Build();
app.MapGet("/poster", () => Canvas.Poster);
app.MapGet("/schedule", () => new { Description = "Exhibition", Day = DayOfWeek.Monday });
app.Run();
Esta vez llamamos a ConfigureHttpJsonOptions para registrar el convertidor de enumeraciones a cadenas. Nos da el mismo objeto Microsoft.AspNetCore.Http.Json.JsonOptions al que llega una llamada directa a Configure<JsonOptions>, solo que mediante una extensión dedicada. Además, mapeamos dos rutas: poster y schedule. Estos endpoints funcionan igual que los del controlador anterior, y la salida también es idéntica.
Como vemos, podemos configurar la aplicación para que serialice las enumeraciones como cadenas de forma predeterminada, ¡sin decorar los modelos! Así podemos trabajar con modelos limpios. Además, no tenemos que preocuparnos de indicar opciones explícitas cada vez que serializamos un objeto.
Configurar la serialización con generación de código fuente
La generación de código fuente elimina la generación de código en tiempo de ejecución que JsonStringEnumConverter necesita normalmente, algo importante cuando la aplicación se publica con compilación anticipada (AOT). En un proyecto analizado para AOT, el convertidor no genérico provoca IL3050, y el propio diagnóstico dice que las aplicaciones deberían usar en su lugar el convertidor genérico JsonStringEnumConverter<TEnum>. Un contexto con generación de código fuente es la forma más limpia de conseguirlo:
[JsonSourceGenerationOptions(UseStringEnumConverter = true)] [JsonSerializable(typeof(Canvas))] internal partial class CanvasContext : JsonSerializerContext;
Ahora todas las enumeraciones accesibles desde Canvas se serializan por nombre, sin registrar ningún convertidor en tiempo de ejecución:
var json = JsonSerializer.Serialize(Canvas.Poster, CanvasContext.Default.Canvas);
La propiedad UseStringEnumConverter llegó con .NET 8, así que esta forma requiere .NET 8 o posterior, o bien el paquete System.Text.Json 8.0 y C# 12 si el proyecto tiene como destino una versión anterior.
| Quiero que se aplique a... | System.Text.Json | Newtonsoft.Json |
|---|---|---|
| una propiedad | [JsonConverter(typeof(JsonStringEnumConverter))] en la propiedad | [JsonConverter(typeof(StringEnumConverter))] en la propiedad |
| todos los usos de un tipo de enumeración | el mismo atributo en la declaración enum | el mismo atributo en la declaración enum |
| una llamada al serializador | options.Converters.Add(new JsonStringEnumConverter()) | pasar new StringEnumConverter() a SerializeObject |
| toda una aplicación MVC / Web API | AddControllers().AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(...)) | AddControllers().AddNewtonsoftJson(o => o.SerializerSettings.Converters.Add(...)) |
| toda una aplicación de API mínima | builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(...)) | Sin integración nativa en .NET 10 |
¿Cómo serializar una enumeración como un valor de cadena personalizado?
Al serializar una enumeración como cadena, puede que queramos ajustar algunos detalles. Por ejemplo, pasarla a camelCase o mostrarla como un texto más significativo, etc. Veamos cómo hacerlo.
Serializar como texto en camelCase
Serializar JSON en camelCase es una práctica habitual. De hecho, es el comportamiento predeterminado en ASP.NET Core. Pero, en general, esto se refiere a pasar a camelCase los nombres de las propiedades, no sus valores, y tenemos un artículo aparte sobre cómo escribir también los nombres de las propiedades en camelCase:
// Native
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
options.Converters.Add(new JsonStringEnumConverter());
var json = JsonSerializer.Serialize(Canvas.Poster, options);
{
"name": "Poster",
"backColor": "LightGray",
"medium": "Water",
"pen": {
"name": "Simple",
"color": "Red"
}
}
Esto se explica solo y es técnicamente lógico, pero puede que en la práctica no sea lo que queremos para las enumeraciones. Por eso, el convertidor de enumeraciones ofrece una forma de pasar explícitamente los valores de la enumeración a camelCase:
// Native var options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); var json = JsonSerializer.Serialize(Canvas.Poster, options); // Newtonsoft var converter = new StringEnumConverter(new CamelCaseNamingStrategy()); var json = JsonConvert.SerializeObject(Canvas.Poster, converter);
Solo tenemos que indicar la política (o estrategia) camelCase al crear la instancia del convertidor de enumeraciones y pasarlo en la serialización como de costumbre:
{
"Name": "Poster",
"BackColor": "lightGray",
"Medium": "water",
"Pen": {
"Name": "Simple",
"Color": "red"
}
}
Ahora todos los valores de la enumeración están en formato camelCase. El convertidor acepta cualquier política de nomenclatura, no solo las integradas, así que también podemos escribir una JsonNamingPolicy personalizada para la estrategia de nomenclatura y pasarla en su lugar.
Serializar como texto personalizado
Debido a las reglas del lenguaje para nombrar variables, un miembro de una enumeración no siempre puede transmitir un texto significativo cuando se serializa de la forma habitual. Para sortear este problema, solemos usar EnumMemberAttribute del ensamblado System.Runtime.Serialization, pensado específicamente para este fin:
using System.Runtime.Serialization;
public record struct ToggleControl(string Name, ToggleType Type);
public enum ToggleType
{
[EnumMember(Value = "Enable/Disable")]
EnableDisable,
[EnumMember(Value = "Visible/Hidden")]
VisibleHidden,
[EnumMember(Value = "Editable/Readonly")]
EditableReadonly,
}
Definimos una enumeración ToggleType con EnumMemberAttribute en sus miembros para ofrecer valores más significativos que sus nombres. También definimos un record ToggleControl que usa esta enumeración.
Newtonsoft respeta directamente ese atributo:
// Newtonsoft
var controls = new ToggleControl[]
{
new("toggle1", ToggleType.EnableDisable),
new("toggle2", ToggleType.VisibleHidden)
};
var json = SerializeWithStringEnum(controls);
[
{
"Name": "toggle1",
"Type": "Enable/Disable"
},
{
"Name": "toggle2",
"Type": "Visible/Hidden"
}
]
Creamos un array de ToggleControl con distintos valores de ToggleType. ¡Al serializarlo, vemos que la salida contiene los textos que queríamos!

Ebook gratis
¿Tu Web API está lista para producción?
33 puntos que comprobar antes de desplegarla, con la solución de cada uno. Un PDF gratuito de 76 páginas para .NET 10.
El ebook está en inglés.
Descarga la checklist gratisPDF gratuito. Un solo correo para enviártelo. Puedes darte de baja cuando quieras.
System.Text.Json ignora [EnumMember], y siempre lo ha hecho. Tiene su propio atributo para lo mismo, JsonStringEnumMemberNameAttribute, que se agregó en .NET 9:
public enum ToggleType
{
[JsonStringEnumMemberName("Enable/Disable")]
EnableDisable,
[JsonStringEnumMemberName("Visible/Hidden")]
VisibleHidden,
}
Con JsonStringEnumConverter registrado, esas son las cadenas que se generan, y la deserialización las vuelve a leer como el miembro correcto. Sin un convertidor registrado, el atributo no hace nada en absoluto y el mismo modelo sigue escribiendo {"Name":"toggle1","Type":0}, así que aquí el convertidor no es opcional.
Antes de que existiera este atributo, las opciones eran una política de nomenclatura personalizada o un convertidor escrito a mano, y sigue valiendo la pena conocer el convertidor para los casos que el atributo no resuelve: aquí explicamos cómo escribir un JsonConverter personalizado para Newtonsoft.Json.
¿Cómo serializar una enumeración con DataContractJsonSerializer?
DataContractJsonSerializer está en System.Runtime.Serialization.Json y es anterior a System.Text.Json. Es el serializador sobre el que se construyeron los endpoints JSON de WCF y los de ASP.NET AJAX, y todavía viene incluido.
No puede serializar una enumeración como cadena. No hay convertidor que registrar ni opción que cambiar: todos los miembros de una enumeración se escriben como su número subyacente.
[EnumMember] no cambia esto. Si decoras un miembro con [EnumMember(Value = "Enable/Disable")], la salida sigue siendo 0.
Eso es lo sorprendente, porque el DataContractSerializer de XML sí lo respeta. La misma enumeración con el mismo atributo da el nombre si se escribe en XML y el número si se escribe en JSON.
La deserialización se comporta igual. Si el payload contiene la cadena, se lanza una excepción en lugar de convertirla, mientras que cualquier valor Int64 se acepta, incluso uno que ningún miembro define.
Así que, si la salida tiene que mostrar un nombre, este no es el serializador adecuado. Tanto JsonStringEnumConverter como StringEnumConverter lo hacen; este no tiene equivalente.
Aquí está todo en un solo ejemplo. En .NET 10, la clase no necesita ninguna referencia a paquetes, ya que se resuelve desde el framework compartido:
global using System.Runtime.Serialization;
global using System.Runtime.Serialization.Json;
global using System.Text;
[DataContract]
public class ToggleSet
{
[DataMember]
public ToggleState Decorated { get; set; }
[DataMember]
public ToggleState Undecorated { get; set; }
}
[DataContract]
public enum ToggleState
{
[EnumMember(Value = "Enable/Disable")]
EnableDisable = 0,
[EnumMember(Value = "Visible/Hidden")]
VisibleHidden = 1,
}
var set = new ToggleSet
{
Decorated = ToggleState.EnableDisable,
Undecorated = ToggleState.VisibleHidden
};
var serializer = new DataContractJsonSerializer(typeof(ToggleSet));
using var stream = new MemoryStream();
serializer.WriteObject(stream, set);
var json = Encoding.UTF8.GetString(stream.ToArray());
Los dos miembros llevan [EnumMember], y los dos salen como números:
{"Decorated":0,"Undecorated":1}
Es el comportamiento documentado, no un accidente. La referencia de Microsoft sobre la serialización JSON independiente lo incluye entre las reglas del serializador: “Los atributos EnumMemberAttribute y NonSerializedAttribute se ignoran si se usan”.
Volver a pasarle la propia cadena del atributo provoca un error en toda regla en lugar de recurrir a una alternativa. Al leer {"Decorated":"Enable/Disable","Undecorated":1} se lanza una SerializationException que indica que el valor no se puede convertir en el tipo Int64. En cambio, un número que la enumeración nunca declara se deserializa sin protestar.
Si cambias al DataContractSerializer de XML, el mismo tipo escribe <Decorated>Enable/Disable</Decorated>. El atributo no lo ignora el modelo de contratos de datos, sino solo su serializador JSON.
Serialización JSON de enumeraciones de marcas
La serialización predeterminada de las enumeraciones de marcas (enumeraciones de máscara de bits) produce una salida todavía más extraña. En lugar de representarse como una combinación de marcas, se muestran como un valor combinado. Si quieres repasar cómo funciona el atributo [Flags], tenemos un artículo dedicado:
[Flags]
public enum TextStyles
{
None = 0,
Bold = 1,
Italic = 2,
Underline = 4,
}
var styles = TextStyles.Bold | TextStyles.Italic | TextStyles.Underline;
var json = Serialize(new { Format = styles });
La salida:
{"Format":7}
Empezamos definiendo una enumeración de marcas TextStyles. Después declaramos styles, que es una combinación de las marcas Bold(1), Italic(2) y Underline(4). Con la serialización predeterminada, cabría esperar al menos algo como “1, 2, 4”. Pero, en su lugar, obtenemos un 7, porque el serializador predeterminado solo tiene en cuenta el valor numérico de la enumeración.
La conversión de enumeraciones a cadenas no tiene esta rareza:
var styles = TextStyles.Bold | TextStyles.Italic | TextStyles.Underline;
var json = SerializeWithStringEnum(new { Format = styles });
Ahora obtenemos un resultado distinto:
{"Format":"Bold, Italic, Underline"}
¡Esa es la salida que queremos! La deserialización vuelve a convertir esa forma separada por comas en la misma combinación, así que una enumeración de marcas sobrevive al viaje de ida y vuelta como cadena.
Conclusión
En este artículo hemos aprendido algunas formas de serializar a JSON una enumeración como cadena. También hemos visto distintas técnicas para personalizar la serialización de una enumeración como cadena.
En resumen: registra JsonStringEnumConverter para System.Text.Json o StringEnumConverter para Newtonsoft.Json, y elige la ubicación que se ajuste al ámbito que necesites. DataContractJsonSerializer es el único serializador que no puede hacerlo de ninguna manera. La conversión en sentido contrario, del texto de vuelta a un miembro, es otra tarea: consulta cómo convertir una cadena o un int de nuevo en una enumeración.
Probado con .NET 10 y Newtonsoft.Json 13.0.4.