csharp-json-serializacion

Cómo usar JSON en C# con System.Text.Json

  • 4 min

El formato JSON (JavaScript Object Notation) es un formato de texto ligero para representar datos estructurados.

JSON se ha comido el mundo. Es ligero, legible por humanos y es el estándar de facto para APIs REST, archivos de configuración (appsettings.json) y bases de datos NoSQL.

En el ecosistema .NET, durante años dependimos de una librería externa maravillosa llamada Newtonsoft.Json (Json.NET). Sin embargo, desde .NET Core 3.0, Microsoft reescribió todo desde cero para crear System.Text.Json.

Esta nueva librería es nativa, segura por defecto y está obsesivamente optimizada para el rendimiento, utilizando estructuras de bajo nivel como Span<T> y UTF8 directamente en memoria.

Serialización básica

La clase estática que usaremos para casi todo es JsonSerializer.

Vamos a partir de una clase modelo sencilla. Recuerda que, para que el serializador las incluya por defecto, las propiedades deben ser públicas.

public class Videojuego
{
    public string Titulo { get; set; }
    public int Anio { get; set; }
    public string[] Plataformas { get; set; }
}
Copied!

Para convertir un objeto a texto JSON:

using System.Text.Json;

var juego = new Videojuego 
{ 
    Titulo = "Hollow Knight", 
    Anio = 2017, 
    Plataformas = new[] { "PC", "Switch", "PS4" } 
};

string json = JsonSerializer.Serialize(juego);

Console.WriteLine(json);
// Salida: {"Titulo":"Hollow Knight","Anio":2017,"Plataformas":["PC","Switch","PS4"]}
Copied!

Por defecto, System.Text.Json minifica el resultado (quita espacios en blanco) para ahorrar bytes en la transmisión de red.

Deserialización y parsing

Para el proceso inverso usamos Deserialize<T>. El serializador intentará asignar las claves del JSON a las propiedades de la clase T.

string jsonEntrada = @"{ ""Titulo"": ""Celeste"", ""Anio"": 2018 }";

// Convertimos el string JSON en un objeto C#
Videojuego juegoRecuperado = JsonSerializer.Deserialize<Videojuego>(jsonEntrada);

Console.WriteLine(juegoRecuperado.Titulo); // Celeste
Copied!

Por defecto, la deserialización es Case Sensitive (distingue mayúsculas). Si el JSON trae "titulo": "Celeste" (minúscula) y tu clase tiene Titulo (mayúscula), no lo mapeará y dejará la propiedad en null. Veremos cómo arreglar esto enseguida.

Configurar el serializador (JsonSerializerOptions)

El comportamiento por defecto es estricto y rápido. Pero el mundo real es caótico. Para adaptarnos, usamos JsonSerializerOptions.

JSON legible y nombres sin distinción de mayúsculas

Si queremos generar archivos de configuración legibles o leer JSONs que vienen de APIs de JavaScript (que usan camelCase), necesitamos esto:

var opciones = new JsonSerializerOptions
{
    WriteIndented = true, // Añade espacios y saltos de línea
    PropertyNameCaseInsensitive = true // Ignora mayúsculas/minúsculas al leer
};

string jsonBonito = JsonSerializer.Serialize(juego, opciones);
/* Salida:
{
  "Titulo": "Hollow Knight",
  "Anio": 2017,
  ...
}
*/
Copied!

Políticas de nombres

En C# usamos PascalCase (MiPropiedad), pero en JSON web se estila camelCase (miPropiedad). Podemos automatizar esta traducción:

var opcionesWeb = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
// Ahora 'Titulo' se serializará automáticamente como 'titulo'
Copied!

Atributos de control

A veces la configuración global no es suficiente. Puede que una API externa te envíe un nombre de propiedad horrible que no quieres usar en tu código C#, o quieras ocultar datos.

Para esto decoramos las propiedades de nuestra clase con atributos de System.Text.Json.Serialization.

  • [JsonPropertyName("nombre_feo")]: Mapea una propiedad de C# a una clave JSON específica.
  • [JsonIgnore]: Omite la propiedad. No se envía ni se lee.
  • [JsonInclude]: Permite incluir miembros que el serializador no tomaría por defecto, como propiedades públicas con setter no público.
using System.Text.Json.Serialization;

public class Usuario
{
    [JsonPropertyName("user_id")] // En el JSON será "user_id"
    public int Id { get; set; }

    public string Nombre { get; set; }

    [JsonIgnore] // Jamás saldrá de aquí
    public string PasswordHash { get; set; }
}
Copied!

Serialización asíncrona con streams

Si tienes que guardar un objeto grande en un archivo o enviarlo por la red, no lo conviertas primero a string.

  1. Objeto a String (gasta más RAM).
  2. String a Archivo.

Es mucho más eficiente escribir directamente del objeto al Stream (flujo de datos), sin pasar por un string intermedio.

using FileStream createStream = File.Create("data.json");

// Escribe directamente en el disco byte a byte
await JsonSerializer.SerializeAsync(createStream, juego);
Copied!

Y para leer:

using FileStream openStream = File.OpenRead("data.json");

// Lee del disco y construye el objeto al vuelo
Videojuego juego = await JsonSerializer.DeserializeAsync<Videojuego>(openStream);
Copied!

Esto reduce drásticamente el uso de memoria (Garbage Collection) en aplicaciones de alto rendimiento.

Trabajar con JSON dinámico (JsonNode)

C# es tipado estático, pero a veces recibimos un JSON que no sabemos qué estructura tiene, o simplemente queremos leer una propiedad sin crear una clase entera para ello.

Desde .NET 6, tenemos la API DOM de JSON con JsonNode. Es similar a trabajar con un Diccionario.

string json = "{\"temperatura\": 25, \"detalles\": { \"sensor\": \"A1\" } }";

// Parseamos a un nodo genérico
JsonNode nodo = JsonNode.Parse(json);

// Navegamos como si fuera un array asociativo
int temp = (int)nodo["temperatura"];
string sensor = (string)nodo["detalles"]["sensor"];

Console.WriteLine($"Sensor {sensor} marca {temp}º");
Copied!