Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 30 de mayo de 2023
.NET

Imaginad una clase como la siguiente, que representa las características básicas de los archivos almacenados en una aplicación:

public class File
{
    public string FileName { get; set; }
    public ulong SizeBytes { get; set; }
}

Y ahora, imaginemos también una clase que hereda de la anterior para modelar específicamente, aunque también de forma resumida, los archivos de vídeo:

public class VideoFile: File
{
    public string Codec { get; set; }
    public TimeSpan Duration { get; set; }
}

Y puestos a imaginar, acabemos con el siguiente método, que retorna la representación JSON del objeto File que recibe como parámetro:

string SerializeFile(File file) => JsonSerializer.Serialize(file);

Gracias al polimorfismo, ese pilar imprescindible de la Programación Orientada a Objetos, podríamos invocar este método con objetos de tipo File, VideoFile o cualquier descendiente de alguno de ambos, puesto que en todos los casos se trata de objetos de tipo File:

var file = new File 
{ 
    FileName = "file.txt", SizeBytes = 1024 
};
Console.WriteLine(SerializeFile(file));

var videoFile = new VideoFile 
{ 
    FileName = "video.mp4", 
    SizeBytes = 1024 * 1024, 
    Codec = "H264", 
    Duration = TimeSpan.FromMinutes(3)
};
Console.WriteLine(SerializeFile(videoFile));

Sin embargo, el resultado de ejecutar el método a veces no será el esperado. De hecho, los mensajes obtenidos por consola al ejecutar el código anterior serían algo como esto:

{"FileName":"file.txt","SizeBytes":1024}
{"FileName":"video.mp4","SizeBytes":1048576}

¡Hey! ¡Se han serializado exactamente de la misma manera! ¿Dónde están las propiedades Codec y Duration de VideoFile? ¿No deberían aparecer en el JSON?

Pues en efecto, no aparecen porque, para evitar una exposición de datos no controlada, únicamente se se incluirán en el resultado los miembros de la clase o interfaz utilizada de forma explícita al serializar.

Pero si estuviéramos interesados en hacerlo, ¿podríamos?

Serialización polimórfica en .NET 6 y anteriores

Para forzar la serialización polimórfica, es decir, del tipo específico del objeto usado en tiempo de ejecución, tenemos dos opciones en versiones anteriores a .NET 7.

La primera de ellas es utilizar una sobrecarga del método Serialize que recibe como parámetro el tipo a considerar en runtime durante la serialización.

string SerializeFile(File file) 
       => JsonSerializer.Serialize(file, file.GetType());

De esta forma, conseguiremos el resultado deseado:

var videoFile = new VideoFile
{
    ... // Completar propiedades
};
Console.WriteLine(SerializeFile(videoFile));
{"Codec":"H264","Duration":"00:03:00","FileName":"video.mp4","SizeBytes":1048576}

Otra opción igualmente sencilla es usar el tipo object. De esta forma, indicaremos al serializador que debe serializar las propiedades del tipo del objeto pasado en tiempo de ejecución. En la práctica, se podría implementar muy fácilmente así:

string SerializeFile(File file) 
       => JsonSerializer.Serialize((object)file);

O bien, siguiendo con en el ejemplo anterior, podría hacerse simplemente recibiendo un object como parámetro en el método SerializeFile, aunque obviamente ya no podremos asegurar que los objetos que nos pasen sea de tipo File o derivado de él:

string SerializeFile(object file) 
       => JsonSerializer.Serialize(file, file.GetType());

Sin embargo, existe una limitación con este enfoque. La serialización polimórfica se realiza exclusivamente sobre el objeto raíz, de forma que sus propiedades serán serializadas siempre usando su tipo, no el del objeto que contiene en runtime.

Veámoslo con un ejemplo. supongamos ahora la siguiente jerarquía de clases:

public class File
{
    public string FileName { get; set; }
    public ulong SizeBytes { get; set; }
    public FileProperties Properties { get; set; }
}

public class FileProperties
{
    public uint Flags { get; set; }
    public DateTime LastModified { get; set; }
}

public class VideoFile : File
{
    public string Codec { get; set; }
    public TimeSpan Duration { get; set; }
}

public class VideoProperties: FileProperties
{
    public DateTime LastPlayed { get; set; }
}

Instanciemos ahora un objecto VideoFile, en cuya propiedad Properties introduciremos un objeto de tipo VideoProperties. Veremos que al serializarlo, la propiedad LastPlayed no es incluida en el resultado:

var videoFile = new VideoFile
{
    FileName = "video.mp4",
    SizeBytes = 1024 * 1024,
    Codec = "H264",
    Duration = TimeSpan.FromMinutes(3),
    Properties = new VideoProperties()
    {
        Flags = 0x01,
        LastModified = new DateTime(2020, 8, 4, 23, 12, 23),
        LastPlayed = new DateTime(2022, 11, 21, 14, 10, 0),
    }
};
Console.WriteLine(JsonSerializer.Serialize((object)videoFile));
Console.WriteLine(JsonSerializer.Serialize(videoFile, videoFile.GetType()));
{"Codec":"H264","Duration":"00:03:00","FileName":"video.mp4",
  "SizeBytes":1048576,"Properties":{"Flags":1,"LastModified":"2020-08-04T23:12:23"}}
{"Codec":"H264","Duration":"00:03:00","FileName":"video.mp4",
  "SizeBytes":1048576,"Properties":{"Flags":1,"LastModified":"2020-08-04T23:12:23"}}

LastPlayed no ha sido incluida en el resultado porque, de nuevo, necesitaríamos polimorfismo al serializar el miembro Properties de la clase base. En este caso, sólo podríamos conseguirlo estableciendo como object dicha propiedad:

public class File
{
    public string FileName { get; set; }
    public ulong SizeBytes { get; set; }
    public object Properties { get; set; } // Tipo "object" necesario para 
                                           // la serialización polimórfica
}

Obviamente, esto resulta bastante incómodo porque estaríamos forzados a usar un object en lugar del tipo FileProperties simplemente para cumplir las exigencias del serializador 😟

Otra opción, por supuesto, sería crear un conversor personalizado, pero eso sería otra historia. Creo que hay buenos ejemplos en la red, pero si os interesa podéis comentarlo y vemos un día en profundidad cómo crear conversores que permitan personalizar al máximo la forma en que se genera el JSON de un tipo concreto.

Afortunadamente, .NET 7 ha venido acompañado de interesantes mejoras al respecto, que comentaremos un el próximo post.

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!