Logo

dev-resources.site

for different kinds of informations.

JSON#: nuestro propio lector/escritor

Published at
12/3/2024
Categories
spanish
chsarp
tutorial
coding
Author
baltasarq
Categories
4 categories in total
spanish
open
chsarp
open
tutorial
open
coding
open
Author
9 person written this
baltasarq
open
JSON#: nuestro propio lector/escritor

En la anterior entrada, ten铆amos una peque帽a estructura de clases con composici贸n entre ellas. Utilizar JSON para guardar estos datos de forma autom谩tica, mediante el serializador, era imposible, ya que para empezar ten铆amos un ciclo entre las referencias, y adem谩s, el tener propiedades de solo lectura hac铆a que la API JSON no recuperarse ninguna informaci贸n. Tambi茅n vimos un par de soluciones sencillas para estos problemas.

Esta es la soluci贸n compleja.

La API JSON de C# contempla el caso en el que nuestra clase resulte tan compleja de guardar que necesitamos nuestros propios mecanismos, totalmente personalizados. Para esto, podemos hacer derivar la clase JsonConverter, donde T es la clase de la que queremos guardar los objetos en formato JSON.

Un ejemplo sencillo

Un ejemplo sencillo podr铆a ser una clase que incorpore fechas, de manera que utilice el formato de fecha local, pero la guarde con el formato ISO 8601, es decir YYYY-MM-DD (donde 'Y' es un d铆gito para el a帽o, 'M' para el mes, y 'D' para el d铆a). De hecho, si intentamos serializar la siguiente clase, nos dar谩 error diciendo que la clase DateOnly no sabe c贸mo serializarla.

public class EntradaDiario {
    public required DateOnly Fecha { get; init; }
    public required string Texto { get; init; }

    public override string ToString()
    {
        return $"{this.Fecha}: {this.Texto}";
    }
}
Enter fullscreen mode Exit fullscreen mode

As铆 que tenemos que crear una clase conversora, que explique a la API JSON c贸mo serializar EntradaDiario.

class EntradaDiarioConverter: JsonConverter<EntradaDiario> {
    public override DateTimeOffset Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
    {
        // m谩s cosas...
    }

    public override void Write(
            Utf8JsonWriter writer,
            EntradaDiario entrada,
            JsonSerializerOptions options)
    {
        // m谩s cosas...
    }
}
Enter fullscreen mode Exit fullscreen mode

B谩sicamente, tenemos que explicar c贸mo leer y escribir una EntradaDiario. Lo m谩s sencillo es escribir JSON, es decir, completar el m茅todo Write().

class EntradaDiarioConverter: JsonConverter<EntradaDiario> {
    // m谩s cosas...
    public override void Write(
            Utf8JsonWriter writer,
            EntradaDiario entrada,
            JsonSerializerOptions options)
    {
        writer.WriteStartObject();
            writer.WriteString( "fecha", $"{entrada.Fecha.Year, 4:D4}-{entrada.Fecha.Month, 2:D2}-{entrada.Fecha.Day, 2:D2}" );
            writer.WriteString( "texto", entrada.Texto );
        writer.WriteEndObject();
    }
}
Enter fullscreen mode Exit fullscreen mode

Los m茅todos de JsonWriter WriteStartObject() y WriteEndObject() escriben las llaves: '{}'. En el medio, escribimos dos cadenas de caracteres con WriteString(), la primera para la fecha con el formato ISO (a la que le damos el nombre 麓fecha'), y la segunda para el texto (que recibe el nombre 麓texto'), escribiendo una entrada del diario completa.

La salida es como sigue.

{"fecha":"2024-12-03","texto":"Prueba"}
Enter fullscreen mode Exit fullscreen mode

La parte de lectura es m谩s compleja, como ya anunci谩bamos m谩s arriba. Ser谩 necesario leer el texto de la entrada y la fecha, pero no sabemos en qu茅 orden vendr谩 (no podemos asumir que solo lo leeremos habiendo sido escrito por nosotros). Deberemos saber si hemos le铆do o no una EntradaDiario y en caso negativo devolver null.

class EntradaDiarioConverter...
// m谩s cosas...
public EntradaDiario? Read(...
        string texto = "";
        DateOnly? fecha = null;
        EntradaDiario? toret = null;

        while ( reader.Read() ) {
            // leer la fecha
            // leer el texto
        }

        if ( fecha != null ) {
            toret = new EntradaDiario{ Fecha = (DateOnly) fecha, Texto = texto };
        }

    return toret;
Enter fullscreen mode Exit fullscreen mode

Debemos tratar de leer el texto de la entrada y la fecha. Lo hacemos en un bucle para evitar imponer un orden de lectura. Entonces al salir del bucle, comprobamos si al menos hemos le铆do la fecha. En ese caso, se crea una objeto entrada que se devuelve, en caso contrario, se va a devolver null. A continuaci贸n, el interior del bucle.

// m谩s cosas...
while ( reader.Read() ) {
    var tokenType = reader.TokenType;

    if ( tokenType == JsonTokenType.PropertyName
      && reader.ValueTextEquals( "fecha" ) )
    {
        reader.Read();
        fecha = DateOnly.ParseExact(
                        reader.GetString() ?? "",
                        new []{ "yyyy-MM-dd" } );
    }

    if ( tokenType == JsonTokenType.PropertyName
      && reader.ValueTextEquals( "texto" ) )
    {
        reader.Read();
        texto = reader.GetString() ?? "";
    }
}
Enter fullscreen mode Exit fullscreen mode

Se utilizan los m茅todos del lector Read(), que lee el siguiente token. Este m茅todo se utiliza en conjunci贸n con la propiedad TokenType para saber si estamos ante el nombre de una propiedad (TokenType.PropertyName) y un m茅todo (ValueTextEquals()), y en caso de reconocer uno de las propiedades a leer, tomamos su valor. En ambos camos leemos una cadena de caracteres, por lo que llamamos a GetString(). Este m茅todo devuelve null si no encuentra una cadena de caracteres, por lo que utilizamos el operador ?? para devolver un cadena vac铆a en caso de que se produzca ese error. En el caso de la fecha, deberemos realizar un paso extra, que consistir谩 en llamar a ParseExact() para leer la fecha desde el formato ISO en el que fue guardado.

Aplic谩ndolo a la biblioteca

En el caso de nuestra anterior entrada, necesitamos crear conversores para las clases Biblioteca y Autor, de forma que trate de manera especial esas colecci贸n de objetos Libro en Autor, y los objetos Autor en Biblioteca.

Ya que el guardado funciona, no proporcionaremos el m茅todo Write(), sino tan solo Read(). Por desgracia, como hemos visto es el m谩s complejo de los dos.

Lo que tenemos que hacer para la clase Biblioteca es leer los objetos Autor, y utilizar el m茅todo Biblioteca.Inserta() para introducirlos dentro del objeto. De forma similar, para la clase Autor necesitamos leer los objetos Libro y llamar con ellos a Autor.Inserta().

En el caso del autor, tenemos que recuperar el nombre del mismo (propiedad Nombre), y su a帽o de nacimiento (propiedad AnnoNac). Finalmente, necesitamos recuperar una colecci贸n de objetos Libro. Afortunadamente, en este punto podemos utilizar JsonSerializer.Deserialize>(), de forma que no ser谩 necesario que nosotros procesemos toda la lista.

public class AutorJsonConverter: JsonConverter<Autor> {
    public override Autor? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        Autor? toret = null;
        string nombre = "";
        int annonac = 1451; // se inventa la imprenta
        ICollection<Libro>? libros = new List<Libro>();

        reader.Read();
        while ( reader.TokenType != JsonTokenType.EndObject ) {
            if ( reader.TokenType == JsonTokenType.PropertyName
              && reader.ValueTextEquals( "Nombre" ) )
            {
                reader.Read();
                nombre = reader.GetString() ?? "";
            }
            else
            if ( reader.TokenType == JsonTokenType.PropertyName
                 && reader.ValueTextEquals( "AnnoNac" ) )
            {
                reader.Read();
                annonac = reader.GetInt32();
            }
            else
            if ( reader.TokenType == JsonTokenType.PropertyName
                 && reader.ValueTextEquals( "Libros" ) )
            {
                libros = JsonSerializer.Deserialize<ICollection<Libro>>(ref reader, options);

                toret = new Autor {
                    Nombre = nombre,
                    AnnoNac = annonac };

                foreach (var libro in libros ?? new List<Libro>())
                {
                    toret.Inserta( libro );
                }
            }

            reader.Read();
        }

        return toret;
    }

    public override void Write(Utf8JsonWriter writer, Autor value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}
Enter fullscreen mode Exit fullscreen mode

Como no sabemos el orden en el que se leer谩n las propiedades, empleamos un bucle y una secuencia de if's para tratar las tres posibilidades: el nombre del autor, su a帽o de nacimiento, y la colecci贸n de libros. En el caso de leer la colecci贸n de libros, se crea el objeto Autor, pues el resto de datos se consideran accesorios.

Se crea entonces el objeto con el nombre y el a帽o de nacimiento le铆dos hasta el momento, se llama sucesivamente a Autor.Inserta() para introducir el libro en la colecci贸n del autor.

Algo muy parecido se hace para la clase Bibliteca, aunque esta vez se trata de leer el nombre de la biblitoeca y la colecci贸n de autores.

public class BibliotecaJsonConverter: JsonConverter<Biblioteca> {
    public override Biblioteca? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        Biblioteca? toret = null;
        string nombre = "";
        ICollection<Autor>? autores = new List<Autor>();

        reader.Read();
        if ( reader.TokenType == JsonTokenType.PropertyName
            && reader.ValueTextEquals( "Nombre" ) )
        {
            reader.Read();
            nombre = reader.GetString() ?? "";
            reader.Read();
            autores = JsonSerializer.Deserialize<ICollection<Autor>>(ref reader, options);

            toret = new Biblioteca { Nombre = nombre };
            foreach (var autor in autores ?? new List<Autor>())
            {
                toret.Inserta( autor );
            }

            reader.Read(); // endobject
            reader.Read();
        }

        return toret;
    }

    public override void Write(Utf8JsonWriter writer, Biblioteca value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}
Enter fullscreen mode Exit fullscreen mode

Como se puede ver, el funcionamiento es muy parecido al anterior.

Referencias

C贸digo fuente con guardado y lectura de JSON mediante JsonConverter para una entrada de un diario.

C贸digo fuente con guardado y lectura de JSON mediante JsonConverter para una biblioteca.

Entrada sobre uso de conversores para la API JSON en microsoft learn.

spanish Article's
30 articles in total
Favicon
C贸mo gestionar tus proyectos de software con Github
Favicon
M谩s All谩 del C贸digo: La Importancia de la Resoluci贸n de Problemas para Destacar como Programador
Favicon
Configurar servidor de archivos local con Ubuntu y Samba
Favicon
Envias Correo Electr贸nicos gratis con Resend Emails
Favicon
Git avanzado: 驴Qu茅 es cherry pick? 馃崚
Favicon
Desplegar un contenedor de Docker desde Azure Container Registry en una WebApp
Favicon
C贸mo puedes llegar a ser AWS Cloud Club Captain (consejos de una capitana!)
Favicon
驴Elegimos el lenguaje de programaci贸n o el destino lo elige por nosotros?
Favicon
Proyecto Lombok en Java
Favicon
Crear software: Juego de personas
Favicon
"Escapando" de IntelliJ
Favicon
Las claves para NO romperlo TODO: Branches en Git 馃毃馃攽
Favicon
Git: haz que todos amen leer tus commits
Favicon
驴Qu茅 son los enums en Java?
Favicon
Puede la IA reemplazar los trabajos de desarrollo? Vamos a hablar de ello!
Favicon
驴Para qu茅 sirve String[] args en el m茅todo main de Java?
Favicon
La dualidad de tu profesi贸n.
Favicon
C贸mo instalar el JDK de Java usando Temurin en Windows
Favicon
Aprende a programar con Python y Thonny 馃悕
Favicon
隆Primeros pasos en GIT! GIT para PRINCIPIANTES
Favicon
驴Qu茅 es GIT, por qu茅 TODOS los developers lo usan?: Historia de GIT
Favicon
Try with resources en Java
Favicon
Redes neuronales convolucionales (CNN) y redes neuronales recurrentes (RNN)
Favicon
JSON Web Tokens (JWT): Gu铆a Esencial y Buenas Pr谩cticas
Favicon
C贸mo instalar el OpenJDK de Java en Windows
Favicon
C贸mo Desarroll茅 MemoMate: Un Asistente Personal en Telegram con IA
Favicon
C贸mo ver los campos de tu WordPress de un modo diferente
Favicon
Construyendo un Sistema de Asistencia con Reconocimiento Facial Usando Next.js y FACEIOm
Favicon
JSON#: nuestro propio lector/escritor
Favicon
Recursos para AWS Certified Cloud Practitioner

Featured ones: