Para establecer valores predeterminados globales de JsonSerializerOptions en ASP.NET Core, configúralos una sola vez al arrancar: AddJsonOptions() para los controladores, ConfigureHttpJsonOptions() para los endpoints de API mínimas o AddNewtonsoftJson() para volver a dejar la serialización en manos de Json.NET.
De forma predeterminada, ASP.NET Core serializa JSON con System.Text.Json, no con Newtonsoft.Json. Es así desde ASP.NET Core 3.0, y es lo que hace hoy una Web API creada con .NET 10. A continuación, configuramos las tres opciones y mostramos a qué endpoints llega cada una.
Antes de entrar en materia, te recomendamos leer nuestro artículo sobre serialización y deserialización en C#.
Ahora, sigamos.
¿Qué es JsonSerializerOptions?
JsonSerializerOptions es el objeto de configuración que lee System.Text.Json cada vez que serializa o deserializa un objeto. Está en el espacio de nombres System.Text.Json y se pasa como parámetro a JsonSerializer.Serialize() y JsonSerializer.Deserialize().
Cinco propiedades hacen la mayor parte del trabajo. PropertyNamingPolicy decide el uso de mayúsculas y minúsculas en los nombres de las propiedades, de modo que JsonNamingPolicy.CamelCase convierte ReleaseDate en releaseDate. DefaultIgnoreCondition establecido en JsonIgnoreCondition.WhenWritingNull quita de la salida las propiedades null. Encoder controla cómo se escapan los caracteres, algo importante para el contenido que no es seguro en HTML. Converters contiene las instancias de JsonConverter que manejan los tipos que la configuración predeterminada no resuelve bien, como un formato de fecha personalizado. WriteIndented añade saltos de línea y sangría, algo útil en desarrollo y un desperdicio en producción.
En ASP.NET Core rara vez creamos este objeto nosotros mismos. El framework mantiene una instancia por pipeline y se la pasa a los formateadores que escriben las respuestas. Por eso, establecer las opciones de forma global significa recurrir al objeto de opciones del framework, y no a un JsonSerializerOptions que creamos con new en un controlador.
Cuatro de esas cinco merecen que las veamos con más detalle.
Establecer DefaultIgnoreCondition en JsonIgnoreCondition.WhenWritingNull no solo sirve para obtener una salida más limpia: omitir las propiedades null reduce el tamaño del payload y puede mejorar el rendimiento.
La propiedad Encoder ayuda a prevenir ataques XSS al codificar correctamente los datos JSON, normalmente con JavaScriptEncoder.Default.
La propiedad Converters es una lista de instancias de JsonConverter que usamos para personalizar la serialización de ciertos tipos que, de forma predeterminada, no se serializan como esperamos. Es útil para tipos como DateTime o para objetos de negocio personalizados.
La propiedad WriteIndented da formato a la salida JSON con sangrías y saltos de línea, lo que la hace más legible. Aunque esta propiedad favorece la legibilidad durante el desarrollo, normalmente la desactivamos en producción para reducir el tamaño del payload.
¿Cuál es el serializador JSON predeterminado en ASP.NET Core?
ASP.NET Core usa System.Text.Json de forma predeterminada. Es así desde ASP.NET Core 3.0 y lo sigue siendo en .NET 10, donde un proyecto dotnet new webapi nuevo serializa con él y no instala ningún paquete JSON.
Antes de eso, el serializador predeterminado era Newtonsoft.Json. ASP.NET Web API 2 sobre .NET Framework serializaba con Json.NET, y lo mismo hacían ASP.NET Core 1.x y 2.x, que lo incorporaban a través de los paquetes de MVC. Por eso tanto código de ejemplo antiguo y tantas respuestas antiguas de Stack Overflow dan por hecho que se usa. Nada de eso se aplica a un proyecto creado hoy.
ASP.NET Core sigue admitiendo Newtonsoft.Json por completo, pero hay que activarlo explícitamente. Instalar el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson y llamar a AddNewtonsoftJson() sobre AddControllers() hace que los formateadores de entrada y salida vuelvan a usar Json.NET.
El serializador activo determina qué objeto de opciones configuramos. System.Text.Json lee JsonSerializerOptions; Newtonsoft.Json lee JsonSerializerSettings. Establecer propiedades en el objeto equivocado es el motivo habitual por el que un cambio global parece no tener ningún efecto.
| Framework | Serializador JSON predeterminado | Cómo cambiarlo |
|---|---|---|
| ASP.NET Web API 2 (.NET Framework) | Newtonsoft.Json (Json.NET) | Reemplazar JsonMediaTypeFormatter |
| ASP.NET Core 1.x a 2.x | Newtonsoft.Json (Json.NET), una dependencia de MVC | AddJsonOptions() sobre AddMvc() |
| ASP.NET Core 3.0 a .NET 10 | System.Text.Json | AddJsonOptions(), o AddNewtonsoftJson() para volver a Json.NET |
Si la biblioteca en sí es nueva para nosotros, vale la pena empezar por una introducción a System.Text.Json con ejemplos antes de configurar nada de forma global.
¿Cómo configurar opciones JSON globales para los controladores?
Las acciones de controlador leen su configuración JSON de Microsoft.AspNetCore.Mvc.JsonOptions. Si establecemos estas opciones una sola vez al arrancar, garantizamos un manejo uniforme de los datos JSON en todas las acciones de la aplicación.
Para empezar, vamos a crear una clase Product en el proyecto Web API, generado con dotnet new webapi --use-controllers:
public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public DateTime ReleaseDate { get; set; }
public Manufacturer Manufacturer { get; set; } = new Manufacturer();
}
Vamos a crear también una clase Manufacturer:
public class Manufacturer
{
public string? Name { get; set; }
public string? Location { get; set; }
}
Con esto definimos dos clases de modelo para guardar las propiedades del producto.
Ahora, vamos a añadir using System.Text.Encodings.Web;, using System.Text.Json; y using System.Text.Json.Serialization; al principio de Program.cs y a configurar las opciones de serialización JSON en la clase Program:
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.WriteIndented = false;
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Default;
options.JsonSerializerOptions.AllowTrailingCommas = true;
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
});
Aquí encadenamos AddJsonOptions() a AddControllers(), y así configuramos Microsoft.AspNetCore.Mvc.JsonOptions, el objeto de opciones que MVC pasa a sus formateadores.

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.
builder.Services.Configure<JsonOptions>() llega al mismo objeto, pero solo si el tipo JsonOptions que está en el ámbito es el correcto, y cuál es depende de la directiva using que haya al principio del archivo. La forma encadenada deja claro qué objeto de opciones configura, así que es la preferible.
Primero, establecemos todas las propiedades esenciales. Además, activamos AllowTrailingCommas para que el analizador sea más flexible. Después, asignamos a NumberHandling el valor JsonNumberHandling.AllowReadingFromString, que ASP.NET Core ya usa de forma predeterminada, para permitir convertir los números escritos como cadenas JSON en sus tipos numéricos correspondientes.
Como uno de estos ajustes decide el uso de mayúsculas y minúsculas en todas las respuestas que envía la API, vale la pena leer cómo se comporta la serialización de nombres de propiedades en camelCase en todo un proyecto y cómo escribir una política de nomenclatura personalizada permite extenderlo a una convención que el framework no incluye.
Ahora, vamos a crear una clase ProductController y a definir un método POST:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpPost]
public ActionResult CreateProduct(Product product)
{
return Ok(product);
}
}
Aquí creamos un método POST sencillo que recibe el objeto Product como parámetro de entrada y lo devuelve sin ninguna modificación.
Veamos los datos JSON del producto que enviamos a la API en el cuerpo de la solicitud:
{
"Id": 1,
"Name": null,
"Price": 0,
"Quantity": "5",
"ReleaseDate": "2024-04-14T10:49:31.813Z",
"Manufacturer": {
"Name":"Apple",
"Location" : "California"
}
}
Escribimos las propiedades JSON en PascalCase. Además, establecemos la propiedad Name en null y escribimos la propiedad Quantity como cadena.
Ahora, veamos la respuesta, que aquí mostramos con sangría para que se lea mejor:
{
"id": 1,
"price": 0,
"quantity": 5,
"releaseDate": "2024-04-14T10:49:31.813Z",
"manufacturer": {
"name": "Apple",
"location": "California"
}
}
El serializador JSON pasa los nombres de PascalCase a camelCase y excluye la propiedad Name por el ajuste JsonIgnoreCondition.WhenWritingNull que hemos establecido.
La propiedad NumberHandling interpreta Quantity como un número pese a que viene como cadena, y AllowTrailingCommas permite que el analizador acepte una coma final en el payload en lugar de rechazarlo.
¿Cómo configurar opciones JSON globales para las API mínimas?
Cuando creamos API mínimas, es fundamental tener un control preciso de la configuración de serialización JSON específicamente para las solicitudes y respuestas HTTP, es decir, aplicar una configuración de serialización JSON concreta exclusivamente al JSON que nuestros endpoints leen de los clientes y les devuelven.
El método de extensión ConfigureHttpJsonOptions() nos permite personalizar las opciones JSON que se aplican exclusivamente al pipeline HTTP, de modo que esta configuración queda aislada de otras partes de la aplicación.
A continuación, vamos a configurar el método ConfigureHttpJsonOptions() en la clase Program:
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.SerializerOptions.WriteIndented = false;
options.SerializerOptions.Encoder = JavaScriptEncoder.Default;
options.SerializerOptions.AllowTrailingCommas = true;
options.SerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
});
Aquí añadimos el método ConfigureHttpJsonOptions() a IServiceCollection. Del mismo modo que antes, establecemos las propiedades de serialización JSON.
La documentación de Microsoft sobre las API mínimas lo dice en una sola línea: “Las opciones se pueden configurar de forma global para una aplicación llamando a ConfigureHttpJsonOptions”.
Después, vamos a definir una API mínima en la clase Program:
app.MapPost("api/Product/create", (Product product) =>
{
return product;
});
La configuración de serialización que hemos definido se aplicará a esta respuesta.
¿Cómo usar Newtonsoft.Json de forma global en ASP.NET Core?
Bastan dos pasos para que Newtonsoft.Json sea el serializador de todos los controladores. Instala el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson y, después, encadena AddNewtonsoftJson() a AddControllers() y establece en options.SerializerSettings las propiedades que quieras.
Esa única llamada reemplaza los formateadores de entrada y salida de MVC. A partir de ahí, todas las acciones de controlador serializan y deserializan con Json.NET, sin cambiar nada en las propias acciones y sin ninguna llamada a JsonConvert en nuestro código.
JsonConvert.DefaultSettings es otra cosa, y confundir ambos es un error habitual. Afecta a las llamadas directas a JsonConvert.SerializeObject() y JsonConvert.DeserializeObject() que escribimos nosotros mismos. Una acción que devuelve un objeto sigue pasando por el formateador que tenga registrado MVC, así que establecer solo DefaultSettings no cambia nada de la respuesta que recibe el cliente.
Recurre a Newtonsoft.Json cuando necesites lo que todavía hace mejor: DateFormatString, DefaultValueHandling y un ecosistema de convertidores construido durante una década de uso.
Newtonsoft.Json, también llamado Json.NET, nos ofrece más opciones que System.Text.Json para dar formato a las fechas y manejar los valores null. Por ejemplo, puede serializar fechas como cadenas con distintos formatos y manejar los valores null de varias formas (ignorarlos, incluirlos o rechazarlos).
Como primer paso, vamos a instalar el paquete Microsoft.AspNetCore.Mvc.NewtonsoftJson:
dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

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.
Con using Newtonsoft.Json; y using Newtonsoft.Json.Serialization; al principio de Program.cs, vamos a registrar Json.NET para todos los controladores en la clase Program:
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.Formatting = Formatting.Indented;
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.DateFormatString = "dd-MM-yyyy";
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore;
});
Después, con CamelCasePropertyNamesContractResolver() establecemos camelCase como estrategia de nomenclatura predeterminada para las claves JSON. También damos formato al JSON de salida con sangrías para que se lea mejor. A continuación, omitimos del JSON resultante cualquier propiedad null del objeto y serializamos la fecha como cadena con el formato “día-mes-año”.
Por último, establecemos el manejo de los valores predeterminados de las propiedades en DefaultValueHandling.Ignore, lo que hace que las propiedades con valores predeterminados (como 0 para los enteros, false para los booleanos, etc.) queden excluidas de la serialización.
Esta única llamada es excluyente: reemplaza los dos formateadores de MVC, así que las opciones de System.Text.Json que hemos establecido antes dejan de aplicarse a las acciones de controlador en cuanto se registra.
Vamos a definir un endpoint POST en la clase ProductController:
[HttpPost("save")]
public ActionResult SaveProduct(Product product)
{
return Ok(product);
}
En esta acción, devolvemos el objeto product y dejamos que lo serialice el formateador registrado. No hay ninguna llamada a JsonConvert, y precisamente para eso configuramos el serializador de forma global.
Veamos, entonces, el cuerpo de la solicitud:
{
"Id": 1,
"Name": null,
"Price": 0,
"Quantity":"5",
"ReleaseDate": "2024-04-14T10:49:31.813Z",
"Manufacturer":
{
"Name":"Apple",
"Location" : "California"
}
}
Incluimos propiedades JSON con nombres en PascalCase y valores predeterminados en las propiedades Name y Price.
Comprobemos la respuesta:
{
"id": 1,
"quantity": 5,
"releaseDate": "14-04-2024",
"manufacturer": {
"name": "Apple",
"location": "California"
}
}
La propiedad Name se omite en la respuesta porque contiene un valor null. La serialización da a la propiedad ReleaseDate el formato ‘dd-MM-yyyy’, y la propiedad Price se ignora porque le hemos asignado el valor predeterminado 0.
¿Qué objeto de opciones JSON se aplica a qué endpoints?
ASP.NET Core tiene dos objetos de opciones JSON independientes y, para mayor confusión, los dos tipos se llaman JsonOptions.
Microsoft.AspNetCore.Mvc.JsonOptions se usa para las acciones de controlador y expone su configuración a través de una propiedad llamada JsonSerializerOptions. Microsoft.AspNetCore.Http.Json.JsonOptions se usa para los endpoints de API mínimas, Results.Json() y TypedResults, y expone su configuración a través de una propiedad llamada SerializerOptions. A cuál de los dos llega una simple llamada a Configure<JsonOptions>() depende por completo de la directiva using que haya al principio del archivo.
Ninguno de los dos hereda la configuración del otro. Configurar uno deja el otro con sus valores predeterminados, así que una política de nomenclatura establecida con AddJsonOptions() cambia lo que devuelve un controlador y nunca llega a los endpoints de API mínimas, y ConfigureHttpJsonOptions() cambia lo que devuelve un endpoint de API mínima y nunca llega a los formateadores de MVC. Una aplicación que tenga los dos tipos de endpoints tiene que configurar ambos.
Nuestras propias llamadas a JsonSerializer.Serialize() no leen ninguno de los dos objetos. Usan la instancia de opciones que les pasamos o, si no les pasamos ninguna, los valores predeterminados de la biblioteca, y por eso un cambio global nunca les llega.
Qué formateador llega a ejecutarse se decide un paso antes, cuando la negociación de contenido elige el formateador para la solicitud.
Buenas prácticas y consideraciones
Cuando cambiamos la configuración de serialización JSON de forma global, es fundamental evaluar con cuidado los posibles efectos sobre el código existente para evitar consecuencias no deseadas. El cambio puede alterar el comportamiento de endpoints de la API que los clientes ya consumen, y llegar a romper contratos si los clientes dependen del formato de serialización actual. Deberíamos aplicar los cambios de forma gradual, con pruebas exhaustivas e interruptores de funcionalidades (feature toggles), que pueden ayudar a evitar alteraciones en el comportamiento del sistema.
La coherencia es clave en las prácticas de serialización JSON de toda la aplicación. Facilita la comprensión y ayuda a prevenir errores. Debemos centralizar y documentar la configuración de serialización para poder esperar un comportamiento uniforme en toda la aplicación. Los dos tipos JsonOptions son clases de opciones normales, así que el patrón de opciones que usan estos objetos de configuración se les aplica como a cualquier otra.
Cuando los datos serializados forman parte de un contrato con sistemas externos o deben almacenarse a largo plazo, es crucial garantizar la compatibilidad y un control de versiones cuidadoso. Deberíamos introducir los cambios mediante API versionadas y tener en cuenta el impacto en el almacenamiento de datos, en el procesamiento y en los consumidores externos. Este enfoque estratégico preservará la integridad de los datos y facilitará una evolución fluida del código a medida que surjan nuevas buenas prácticas. Para algunos consumidores, el orden de las propiedades forma parte de ese contrato, y controlar el orden en que se escriben las propiedades es otra cuestión de JsonSerializerOptions que conviene resolver una sola vez.
Si pensamos en el futuro de .NET, debemos tener en cuenta estos factores adicionales. System.Text.Json, de Microsoft, es el serializador predeterminado de las nuevas aplicaciones .NET. Aunque optemos por usar Newtonsoft.Json, deberíamos estar atentos a los posibles cambios en las buenas prácticas y prepararnos para adaptar nuestra estrategia a medida que evolucione el ecosistema.
Conclusión
En este artículo, hemos visto distintas formas de configurar la serialización JSON en ASP.NET Core, tanto con las opciones nativas de System.Text.Json como con las funcionalidades más completas de Newtonsoft.Json.
Probado con .NET 10.0.10 (SDK 10.0.302), Newtonsoft.Json 13.0.4 y Microsoft.AspNetCore.Mvc.NewtonsoftJson 10.0.11.