Archivo de la etiqueta: json

Serializar/deserializar con Json.Serialization (ejemplo para C#)

Pues eso… ahora esto de los ficheros con la extensión .json es lo que está en la «onda» y… pues habrá que aprovecharlo que incluso es fácil usarlo en las aplicaciones de .NET.

Espero que no sirva de precedente, pero en este post no hay ejemplo para Visual Basic, solo para C#. No es porque yo abandone mis END IFs y me pase definitivamente a las llaves y puntos y comas, es porque acabo de terminar una clase «serializable» y la he escrito en C# y por vagancia, no he querido crear un ejemplo en Visual Basic. Eso sí, esta clase la utilizo desde código de Visual Basic, ya que en C# solo he hecho la clase, y como he comprobado que los ejemplos que he consultado no hacían bien el trabajo, me he decidido a escribir esta entra en el blog (post).

La clase que serializo es muy simple, es para «recordar» los tamaños de las columnas de, en mi caso, un DataGridView. Esa cuadrícula utiliza distintos tipos de nombres y anchos de columnas, por tanto, el objeto que contiene los valores está formado por un diccionario en el que la clave es una cadena (string), para saber el tipo de datos mostrados, y los valores es otro diccionario de tipo entero en la clave y el valor que guardará serán el índice de la columna y el ancho de la misma.

Esa propiedad está definida de la siguiente forma:

/// <summary>
/// Diccionario para el tipo de listado y los valores de cada columna 
/// (por índice) y el ancho de la columna.
/// </summary>
[JsonPropertyName("anchos")]
public Dictionary<string, Dictionary<int, int>> Anchos { get; set; } = new();

El atributo JsonPropertyName es el que le indica al compilador que esa propiedad es serializable y que está enlazada con el valor anchos del fichero .json.

Esta es la parte fácil.

Ahora hay que leer y guardar los datos en el fichero de texto con la extensión que queramos, pero que en este ejemplo utilizo el valor estándar: .json.

Y estos son los métodos principales para guardar (Save) y leer (Load) el contenido de la clase en el fichero. Save guarda el contenido de la clase en el fichero (lo serializa) y Load lee el contenido del fichero (lo de-serializa) y lo asigna a un objeto del tipo de la clase del tipo donde está definida esa propiedad. No te líes. Es más sencillo el código que explicar lo que hace… 😉

/// <summary>
/// Carga los valores del fichero indicado.
/// </summary>
/// <param name="fileName">El path del fichero a leer y devolver el contenido.</param>
/// <returns>El objeto leído del fichero indicado.</returns>
private static AnchoColumnas Load(string fileName)
{
    // Abrir el fichero para leer, compartido para lectura y escritura.
    using var stream = new FileStream(fileName, 
                                      FileMode.OpenOrCreate, 
                                      FileAccess.Read, 
                                      FileShare.ReadWrite);
    // Si tiene contenido, deserializarlo, si no, devolver un valor nulo.
    if (stream.Length > 0)
        return JsonSerializer.Deserialize<AnchoColumnas>(stream);
    else
        return null;
}

/// <summary>
/// Guarda los datos de tipo AnchoColumnas indicado.
/// </summary>
/// <param name="anchosColumnas"></param>
/// <param name="fileName"></param>
/// <returns></returns>
private static void Save(AnchoColumnas anchosColumnas, string fileName)
{
    // Abrir el fichero para escribir, compartido para lectura y escritura.
    using var stream = new FileStream(fileName, 
                                      FileMode.OpenOrCreate, 
                                      FileAccess.Write, 
                                      FileShare.ReadWrite);
    // Que se indente el contenido.
    var options = new JsonSerializerOptions { WriteIndented = true };
    // Guardar (serializar) el contenido de la clase.
    JsonSerializer.Serialize<AnchoColumnas>(stream, anchosColumnas, options);
}

En mi caso, he creado otros dos métodos llamados Guardar y Leer en el que asigno el nombre del fichero que contendrá los datos serializados de la clase.
Ese fichero está en el path del ejecutable.

Este es el código:

/// <summary>
/// Guardar los datos de los anchos de los listados.
/// </summary>
/// <param name="anchosColumnas"></param>
public static void Guardar(AnchoColumnas anchosColumnas)
{
    // El fichero está en la carpeta del ejecutable.
    var fic = Path.Combine(Application.StartupPath, "AnchosColumnas.json");
    Save(anchosColumnas, fic);
}

/// <summary>
/// Leer los anchos de las columnas.
/// </summary>
/// <returns></returns>
public static AnchoColumnas Leer()
{
    // El fichero está en la carpeta del ejecutable.
    var fic = Path.Combine(Application.StartupPath, "AnchosColumnas.json");
    return Load(fic);
}

Como puedes comprobar, el método Guardar recibe como parámetro la clase a serializar y el método Leer devuelve un objeto con la clase deserializada (o un valor nulo en caso de que aún no tenga contenido el fichero).

La parte interesante está en los métodos Save y Load, ya que utilizo código «seguro» a la hora de leer o escribir en un fichero que no exista.
Y el truco está en crear el objeto de tipo Stream usando FileStream en lugar de usar los métodos OpenRead u OpenWrite de la clase File, ya que, para usar esos dos métodos habría que hacer comprobaciones si existen, si están compartidos, etc., etc., etc.

Por último, en esa misma clase tengo un método (también compartido o estático) para acceder a la información. Es una propiedad de solo lectura, que se encarga de asignar/leer el objeto si debe hacerlo (cuando inicialmente no está asignado).

Este es el código:

private static AnchoColumnas _AnchosColumnas = null;
/// <summary>
/// Los anchos de las columnas de los listados.
/// </summary>
public static AnchoColumnas AnchosColumnas
{
    get
    {
        if (_AnchosColumnas == null)
        {
            _AnchosColumnas = Leer();
            if (_AnchosColumnas == null)
            {
                _AnchosColumnas = new();
            }
        }
        return _AnchosColumnas;
    }

Y finalmente te muestro cómo uso esa clase desde el código que tengo en Visual Basic:

Primero para asignar las columnas al objeto DataGridView (en el código indicado por lvDatos) y después cuándo guardar los nuevos valores, cosa que hago en el evento ColumnWidthChanged.

La asignación de los anchos guardados lo hago en un método (con más código del mostrado) en el que asigno los valores de las columnas, tanto el texto a mostrar como el ancho, y por supuesto el número de las mismas.

Este es el código para leer los valores guardados y asignarlos a las columnas del objeto lvDatos:

' Asignar los anchos que estén guardados.           (22/abr/22 20.34)
If AnchoColumnas.AnchosColumnas.Anchos.ContainsKey(value.ToString()) = False Then
    AnchoColumnas.AnchosColumnas.Anchos.Add(value.ToString(),
                                            New Dictionary(Of Integer, Integer))
End If
Dim cols = AnchoColumnas.AnchosColumnas.Anchos(value.ToString())
If cols.Keys.Count = 0 Then
    For i = 0 To lvDatos.Columns.Count - 1
        cols.Add(i, lvDatos.Columns(i).Width)
    Next
Else
    For i = 0 To cols.Keys.Count - 1
        lvDatos.Columns(i).Width = cols(i)
    Next

d End If

El valor de la variable «value» es el nombre de la enumeración que utilizo y que es un valor asignado como parámetro de este método (recuerda que esto es solo un extracto en el que se asignan los valores leídos, si es que existe el fichero con esos valores ya guardados).

Y para finalizar, el código del evento ColumnWidthChanged, en el que, tengo puesto una comprobación de si se está inicializando (cuando se carga el formulario) con idea de que no se guarden los valores iniciales que tenga.

Private Sub lvDatos_ColumnWidthChanged(sender As Object, e As DataGridViewColumnEventArgs) Handles lvDatos.ColumnWidthChanged
    ' Cambiar el ancho de las columnas de los totales       (10/mar/22 04.28)
    ' al cambiar el del principal.
    If inicializando Then Return

    ' Asignar y guardar los valores.                        (22/abr/22 20.39)
    Dim value = TipoListado.ToString()
    With lvDatos
        If AnchoColumnas.AnchosColumnas.Anchos.ContainsKey(value) = False Then
            AnchoColumnas.AnchosColumnas.Anchos.Add(value, New Dictionary(Of Integer, Integer))
        End If
        ' Asegurarse que se asignan correctamente.
        Dim cols = AnchoColumnas.AnchosColumnas.Anchos(value)
        cols.Clear()
        For i = 0 To .Columns.Count - 1
            cols.Add(i, .Columns(i).Width)
        Next
        AnchoColumnas.Guardar(AnchoColumnas.AnchosColumnas)
    End With
End Sub

Y esto es todo… otro día pondré el código para Visual Basic de la clase y un ejemplo de cómo usarla escrito en C#, pero eso será en otra ocasión 😉

Nos vemos.
Guillermo