Al trabajar con datos JSON en aplicaciones C#, la deserialización de objetos es una tarea común. Sin embargo, surge un desafío particular cuando las claves dentro de la estructura JSON contienen caracteres especiales, como @ o $, que no son válidos para nombres de propiedades en C#. Este artículo explora cómo abordar esta situación utilizando los atributos DataContract y DataMember del framework .NET.
El Problema con Claves Especiales
Considere el siguiente fragmento JSON:
{
"codigo" : "PRD001",
"@nombreProducto" : "Laptop Gaming X",
"$precioUnitario" : 1299.99
}
Un intento directo de mapear esto a una clase C# de la forma habitual (por ejemplo, public string codigo { get; set; }) funcionaría para la clave "codigo". Pero, ¿qué pasa con "@nombreProducto" o "$precioUnitario"? No podemos declarar propiedades como public string @nombreProducto { get; set; }.
La Solución: Atributos DataContract y DataMember
La biblioteca System.Runtime.Serialization.Json, en combinación con los atributos DataContract y DataMember, proporciona una solución robusta para este escenario. Estos atributos permiten definir un contrato de datos explícito, donde podemos epsecificar el nombre de la clave JSON asociada a cada propiedad de la clase C#.
A continuación, se muestra cómo definir una clase C# que pueda deserializar el JSON de ejemplo:
using System.Runtime.Serialization;
[DataContract]
public class InfoProducto
{
[DataMember(Order = 0, Name = "codigo")]
public string Identificador { get; set; }
[DataMember(Order = 1, Name = "@nombreProducto")]
public string NombreCompleto { get; set; }
[DataMember(Order = 2, Name = "$precioUnitario")]
public decimal CostoPorUnidad { get; set; }
}
En este ejemplo:
- El atributo
[DataContract]marca la clase como un tipo que participa en un contrato de datos. - El atributo
[DataMember]se aplica a cada propiedad que se desea serializar o deserializar. - La propiedad
Namedentro de[DataMember(Name = "...")es crucial, ya que permite especificar la clave exacta del JSON, incluidos los caracteres especiales. Orderes opcional, pero puede usarse para especificar el orden de los miembros en el JSON serializado.
Ejemplo Completo de Serialización y Deserialización
Para demostrar el proceso completo, consideremos el siguiente código que deserializa el JSON de ejemplo, modifica un valor y luego lo serializa de nuevo a una cadena JSON.
using System;
using System.IO;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
namespace JsonSpecialKeys
{
class Program
{
static void Main(string[] args)
{
// JSON de entrada con claves especiales
string jsonEntrada = "{\"codigo\":\"PRD001\",\"@nombreProducto\":\"Laptop Gaming X\",\"$precioUnitario\":1299.99}";
// Instanciar el serializador para la clase InfoProducto
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(InfoProducto));
Console.WriteLine("--- Deserialización ---");
// Deserializar el JSON a un objeto InfoProducto
InfoProducto miProducto;
using (MemoryStream msInput = new MemoryStream(Encoding.UTF8.GetBytes(jsonEntrada)))
{
miProducto = jsonSerializer.ReadObject(msInput) as InfoProducto;
}
// Mostrar valores deserializados
Console.WriteLine($"Identificador: {miProducto.Identificador}");
Console.WriteLine($"Nombre: {miProducto.NombreCompleto}");
Console.WriteLine($"Costo: {miProducto.CostoPorUnidad:C}");
// Modificar un valor antes de la serialización
miProducto.CostoPorUnidad = 1199.50m;
miProducto.NombreCompleto += " (Oferta)";
Console.WriteLine("\n--- Serialización ---");
// Serializar el objeto InfoProducto de nuevo a una cadena JSON
string jsonSalida;
using (MemoryStream msOutput = new MemoryStream())
{
jsonSerializer.WriteObject(msOutput, miProducto);
msOutput.Position = 0; // Resetear la posición del stream para la lectura
using (StreamReader reader = new StreamReader(msOutput, Encoding.UTF8))
{
jsonSalida = reader.ReadToEnd();
}
}
Console.WriteLine($"JSON Resultante:\n{jsonSalida}");
Console.ReadKey();
}
}
[DataContract]
public class InfoProducto
{
[DataMember(Order = 0, Name = "codigo")]
public string Identificador { get; set; }
[DataMember(Order = 1, Name = "@nombreProducto")]
public string NombreCompleto { get; set; }
[DataMember(Order = 2, Name = "$precioUnitario")]
public decimal CostoPorUnidad { get; set; }
}
}
Este código demuestra cómo la deserialización traduce correctamente las claves JSON con caracteres especiales a las propeidades de C# definidas con DataMember(Name = "..."). Posteriormente, el objeto modificado se serializa de nuevo, manteniendo la estructura original de las claves JSON.