Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=9f6ff106-f64f-42fc-a5b0-38dbafb6c23c

Cómo manejar JSON en .NET con System.Text.Json

Imagen ornamental

Un serializador es una o varias clases especializadas que se encargan de leer y escribir datos entre un objeto determinado y una cadena de texto y viceversa. Así, un serializador JSON es una clase que permite convertir cualquier objeto a una cadena de texto en formato JSON y al contrario: a partir de una cadena de texto en formato JSON es capaz de devolvernos un objeto del tipo adecuado usando alguna clase que tengamos en el código.

Esta es una funcionalidad muy importante ya que nos permite guardar el estado de algunos objetos que usemos en nuestra aplicación en un determinado formato, y luego volver a recuperarlos cuando lo necesitemos. En el caso de JSON es más importante todavía ya que si queremos construir una API REST que intercambie objetos entre el navegador y nuestra aplicación, generalmente lo haremos usando ese formato.

Tradicionalmente .NET no tenía un serializador para JSON propio integrado en la plataforma y el estándar de facto para hacerlo era JSON.NET de Newtonsoft. Desde .NET Core 3.0 tenemos esta funcionalidad ya integrada en la propia plataforma, dentro del espacio de nombres System.Text.Json. Estas clases están basadas en el uso de Span<byte>, por lo que utilizan de manera eficiente la memoria y tienen un enorme rendimiento (mayor que el de JSON.NET), y que con cada versión ha ido mejorando, por lo que ya no es necesario recurrir a bibliotecas externas para manejar datos en formato JSON. Además hace especial hincapié en ser compatible con la especificación JSON, utiliza UTF-8 y tiene incluso un modelo de acceso en memoria en forma de documento para acceder las partes concretas que nos interesen.

En este artículo te voy a contar cómo puedes utilizar este espacio de nombres para convertir tus clases a JSON y viceversa, controlando además algunas opciones para hacerlo.

Nota: todo lo explicado parte de la base de que estás usando .NET en sus versiones "modernas" (.NET 6 o posterior idealmente). Pero si necesitas usar esto con versiones antiguas de .NET y particularmente .NET 4.x, existe una versión instalable en forma de paquete NuGet para que puedas usarlo igualmente. Así que, en realidad, es una funcionalidad a la que le puedes sacar partido siempre.

Convertir una clase existente a JSON con .NET

La clase responsable de convertir un objeto de alguna de tus clases en JSON es JsonSerializer.

Para ver cómo funciona vamos a considerar una clase User para nuestra aplicación que se encarga, supuestamente, de almacenar datos de un usuario y que, a efectos de este ejemplo, tiene un poco de todo. Su definición sería la siguiente:

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Boolean IsAlive { get; set; }
    public List<string> Languages { get; set; }
    public DateTime LastAccess { get; set; }
    public Dictionary<string, Address> Addresses { get; set; }
    public string ExtraInfo { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

Fíjate en que hemos definido también una clase Address que nos permite crear una propiedad "compleja" para User con las direcciones postales del usuario.

Con estas clases podemos instanciar un nuevo objeto de tipo User que contenga los datos de un determinado usuario, por ejemplo así:

User user = new User()
{
    Name = "João Soares",
    Age = 30,
    IsAlive = true,
    Languages = new List<string>() { "Portugues", "Ingles" },
    LastAccess = DateTime.Now,
    Addresses = new Dictionary<string, Address>()
    {
        { "Home", new Address() { Street = "Rua dos Lobos, 1", City = "Lisboa" } },
        { "Office", new Address() { Street = "Avenida de Vigo, 34", City = "Madrid", Zip = "28080" } }
    }
};

Lo que hace un serializador es tomar ese objeto y convertirlo en una cadena de texto con el formato adecuado. En nuestro caso en formato JSON, fácil de interpretar y utilizar por JavaScript en el navegador.

Con la clase JsonSerializer no podría ser más fácil obtener dicha cadena JSON, ya que basta con hacer esto:

var userJson = JsonSerializer.Serialize<User>(user);

¡Así de simple! En la cadena userJson tendríamos el contenido del usuario, que si lo mostramos por consola lo veríamos así:

El JSON generado, todo en una línea

Fíjate en cómo anida las propiedades complejas, por ejemplo las direcciones, que también la serializa del mismo modo.

Este JSON es estupendo para trasegarlo entre el servidor y el cliente, pero tiene un par de problemas:

  1. Está usando en formato Pascal Case (o sea, con la primera letra de cada palabra en mayúscula) que es el que tenemos por convención en C#. Pero en JavaScript la convención es utilizar nombres en formato Camel Case (la primera letra minúscula, y las de las demás palabras del nombre en mayúscula). No es importante y JavaScript será capaz de usarlo igualmente, pero es "feo" y no es lo que esperará un programador Front-End.
  2. Aunque toda la información comprimida sin espacios está muy bien para ahorrar bytes a la hora de transmitirla, si la queremos para almacenar localmente en un archivo o una base de datos y que sea legible por una persona, no es lo más adecuado. Por eso, podríamos necesitar que se viese en un formato más legible, en varias líneas y con sangrados para los diferentes datos.

Podemos controlar estas opciones a la hora de convertir el objeto a JSON gracias a la clase JsonSerializerOptions, que está en el espacio de nombres subordinado System.Text.Json.Serialization.

Importante: para que todo esto te funcione, lógicamente, tienes que incluir los espacios de nombres mencionados al principio de tu archivo de código, con:

using System.Text.Json;
using System.Text.Json.Serialization;

Podemos establecer las opciones que nos interesen gracias a los miembros de esta clase JsonSerializerOptions. Por ejemplo, si hacemos esto:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};
var userJson = JsonSerializer.Serialize(user, options);

lo que estamos indicando es que queremos que los nombres de las propiedades en el JSON resultante se creen en formato Camel Case, y que se escriba con formato indentado, es decir, en varias líneas y con sangrados. Fíjate en que le pasamos como segundo parámetro del serializador estas opciones.

Resultaría en una cadena de texto como esta:

La cadena ahora está sangrada y tiene nombres camel case

Esto es mucho más legible si lo tiene que inspeccionar una persona, y además lleva los nombres de las propiedades en Camel Case (por ejemplo, isAlive o lastAccess donde la primera letra está en minúsculas).

Un detalle adicional sobre la escritura: existe una versión genérica de este método Serialize en el que le podemos indicar explícitamente la clase que queremos usar para hacer la conversión. Por ejemplo, podríamos hacer escrito: var userJson = JsonSerializer.Serialize<User>(user, options);, donde le indicamos la clase concreta a utilizar. En ese caso no hay diferencia, pero nos puede servir para usar una clase base genérica para serializar varios objetos de clases derivadas, por ejemplo.

Controlar opciones de serialización en la propia clase

Si te fijas bien en el JSON resultantes de la operación anterior verás que, por ejemplo, hay ciertas propiedades que no estaban definidas y que se han escrito igualmente en la conversión pero con valor nulo.

Además es posible que, por motivos de reglas de negocio o de integración, queramos que ciertos campos lleven un nombre diferente, o quizá que el orden en el que se escriben sea otro...

Todo esto se puede controlar con sencillez gracias a los atributos específicos que nos ofrece el serializador y que puedes consultar en la documentación oficial.

Así, por ejemplo, si quisiésemos que la propiedad Name se guardase con un nombre diferente, necesario para la aplicación Front-End que lo va a consumir, podríamos decorarlo con el atributo:

[JsonPropertyName("userName")]

Del mismo modo, si quisiésemos que la propiedad IsAlive fuese la última en escribirse, sin importar en qué posición se ha definido, la podríamos decorar con:

[JsonPropertyOrder(1)]

Al ponerle un 1 como valor para el orden, como todas las demás por defecto tienen el valor 0, se escribirán antes y esta irá al final (podemos decorarlas todas y definir su orden exacto como queramos).

También podríamos obviar algunas propiedades a la hora de escribir el JSON si no nos interesa que se serialicen, para lo cual podemos decorarlas así:

[JsonIgnore()]

Con todos estos atributos podemos definir exactamente cómo queremos que almacene la información al convertirla a JSON. En nuestro ejemplo, tras decorar la clase con estos atributos que te acabo de mostrar, quedaría así:

El JSON con las opciones indicadas

Fíjate en cómo ahora el campo correspondiente al nombre se llama userName, isAlive va al final y la propiedad ExtraData que era nula y no nos interesaba guardarla ya no está.

Una vez que la hayamos convertido a una cadena con el JSON, podemos almacenarla en una base de datos, en un archivo o enviarla a otro lado.

Por ejemplo, para escribirla a disco solo tenemos que hacer:

File.WriteAllText(@"joao-soares.json", userJson);

Convirtiendo JSON a una clase de nuestra aplicación

El proceso in verso es súpersencillo también gracias a la misma clase JsonSerializer y el método complementario al anterior Deserialize. Solo tenemos que asegurarnos de usar las mismas opciones con las que hicimos la conversión en el otro sentido:

string json = File.ReadAllText(@"joao-soares.json");
User user1 = JsonSerializer.Deserialize<User>(json, options);

Ahora tendríamos en user1 los datos que había en el archivo joao-soares.json, escrito antes, y podremos usarlos como un objeto cualquiera de nuestra aplicación, porque lo es.

Fíjate que en este caso utilizamos la variante genérica del método, indicándole que queremos deserializarlo en un objeto de tipo User para que sepa exactamente cómo hacerlo.

¡Ya está! Realmente no tiene mucho más que contar para realizar estas dos tareas básicas. Existen otras clases especializadas, por ejemplo para poder manejarlo como un objeto en memoria y poder operar con partes concretas del mismo. Sin embargo, con estas dos "recetas" tienes lo necesario para poder trabajar en un gran número de situaciones, las más comunes.

Si te ha gustado esto, te gustará mucho más todo lo que puedes aprender con nuestro curso de Desarrollo con la plataforma .NET y C#. Es una formación seria y rigurosa con la que hemos formado a fondo en la plataforma .NET a cientos de alumnos, tanto principiantes como con experiencia (y que han aprendido "de oído" con artículos como este). ¿Vas a seguir aprendiendo a saltos, con posts, vídeos de YouTube y StackOverflow? Deja de dar tumbos y aprende de una vez por todas, en serio y con contacto directo con el mismo tutor que ha creado el curso. No te arrepentirás 😉

Descarga el código de ejemplo de este post aquí (ZIP, 2,64 Kb).

¡Espero que te resulte útil!

José Manuel Alarcón Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José Manuel Alarcón
Archivado en: Lenguajes y plataformas

¿Te ha gustado este post?
Pues espera a ver nuestro boletín...

Suscríbete a la newsletter

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.