1. ¿Para qué sirven los atributos en la serialización?
Cuando serializas objetos a JSON o XML, pasa algo parecido a que un robot-aspiradora inspecciona tu habitación. Todo lo que está a la vista (propiedades/fields públicos) va a la "bolsa" (al fichero o a la cadena), y lo demás se ignora. Pero a veces no quieres que el robot muestre el contenido de tu cartera, o prefieres renombrar las cosas —por ejemplo, no en ruso, sino en inglés.
Aquí es donde entran los atributos. Te permiten controlar el proceso de serialización: ocultar lo innecesario, cambiar nombres, aplicar orden, ignorar partes concretas del objeto o decirle al serializer que tienes reglas especiales. Es como poner pegatinas en tus cosas: "no tocar", "importante", "renombrar en JSON".
Tareas típicas de control de serialización
- Cambiar el nombre de una propiedad o campo en el documento de salida (por ejemplo, en JSON en lugar de FirstName hacer "first_name")
- Ocultar algunos campos/propiedades de la serialización o deserialización (por ejemplo, contraseñas, contadores internos)
- Controlar el manejo de valores por defecto o null-valores
- Especificar el orden de los elementos (relevante para XML)
- Describir parámetros adicionales (atributos) para elementos XML
Cada plataforma de serialización —ya sea System.Text.Json, Newtonsoft.Json o XmlSerializer— usa sus propios atributos para esto.
2. Atributos para System.Text.Json: moderno y popular
El serializer JSON clásico del sistema .NET soporta una buena lista de atributos que viven en el namespace System.Text.Json.Serialization.
Los atributos más útiles:
| Atributo | Para qué sirve | Ejemplo de uso |
|---|---|---|
|
Convierte el nombre de la propiedad en JSON | |
|
Excluye completamente la propiedad de la serialización | |
|
Serializa un campo público (no solo propiedad) | |
|
Ignorar la propiedad bajo ciertas condiciones | |
Ejemplos de uso:
using System.Text.Json.Serialization;
public class Person
{
[JsonPropertyName("first_name")]
public string FirstName { get; set; } // El nombre en JSON será 'first_name'
[JsonIgnore]
public string Password { get; set; } // No aparecerá en JSON
[JsonPropertyName("born_year")]
public int? YearOfBirth { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Nickname { get; set; } // Si es null — no aparecerá en JSON
}
Vamos a serializar a esta persona:
var person = new Person
{
FirstName = "Ivan",
Password = "123456",
YearOfBirth = 2000,
Nickname = null
};
string json = JsonSerializer.Serialize(person);
// json: {"first_name":"Ivan","born_year":2000}
Observa: ¡la contraseña y el apodo han desaparecido del JSON! (La contraseña porque se ignora siempre, y el apodo solo si es null).
¿Por qué importa esto? En la práctica no quieres que el cliente (o un atacante) vea datos críticos como contraseñas, tokens, contadores internos o marcas de tiempo internas. Usando [JsonIgnore] y atributos similares lo solucionas en una línea.
3. Atributos para Newtonsoft.Json: la navaja suiza
Si trabajas con Newtonsoft.Json (documentación aquí), tendrás aún más opciones (y a veces más sencillas si vienes de proyectos antiguos).
Atributos principales:
| Atributo | Propósito |
|---|---|
|
Establece el nombre de la propiedad en JSON, así como orden, obligatoriedad, etc. |
|
Excluye campo/propiedad de la serialización |
|
Requiere presencia obligatoria en la deserialización |
|
Permite especificar un converter propio para casos complejos |
|
Controla la serialización de valores por defecto |
Ejemplo práctico:
using Newtonsoft.Json;
public class UserProfile
{
[JsonProperty("login")]
public string Username { get; set; }
[JsonIgnore]
public string InternalNotes { get; set; }
[JsonProperty(Required = Required.Always)]
public string Email { get; set; }
}
Opciones adicionales de JsonProperty — obligatoriedad (Required), orden (Order) y demás. Por ejemplo:
[JsonProperty("id", Order = 1, Required = Required.Always)]
public int Id { get; set; }
4. Atributos para XML: estilo "clásico"
XmlSerializer usa todo un arsenal de atributos propios del namespace System.Xml.Serialization.
Los más comunes:
| Atributo | Para qué sirve |
|---|---|
|
Elemento en XML, con otro nombre |
|
Convierte la propiedad en un atributo del elemento XML |
|
Excluye la propiedad o campo de la serialización XML |
|
Para colecciones — establece el nombre del array XML |
|
Para colecciones — establece el nombre del elemento del array |
|
Cambia el nombre del tag raíz XML |
Ejemplo práctico:
using System.Xml.Serialization;
[XmlRoot("human")]
public class Person
{
[XmlElement("firstname")]
public string Name { get; set; }
[XmlAttribute("years")]
public int Age { get; set; }
[XmlIgnore]
public string Secret { get; set; }
}
var person = new Person { Name = "Anna", Age = 32, Secret = "42" };
Después de la serialización el XML será algo así:
<human years="32"><firstname>Anna</firstname></human>
Fíjate que el campo Secret no apareció en el XML, y Age se serializó como atributo, no como tag anidado.
5. Matices útiles
Bajo el capó: cómo funcionan los atributos
Cuando el serializer encuentra tu clase, literalmente la "lee" por reflexión (la magia de .NET, más info en System.Reflection). El serializer lee los metadatos: por ejemplo, si la propiedad tiene el atributo JsonIgnore o XmlElement. Dependiendo de eso añade datos al documento final (o los omite).
Es una forma cómoda de separar el "esquema de datos" de la lógica de negocio. Tu clase es tu lógica, y los atributos son, en esencia, el pasaporte para la serialización.
Tabla de correspondencia de atributos clave
| Función | System.Text.Json | Newtonsoft.Json | XmlSerializer |
|---|---|---|---|
| Cambiar nombre | |
|
|
| Ignorar | |
|
|
| Formato custom | |
|
— (a través de IXmlSerializable, doloroso) |
| Raíz del objeto | — | — | |
| Colección | — | — | |
6. Escenarios avanzados
A veces necesitas serializar un objeto de una forma que el serializer por defecto no hace. Por ejemplo, quieres almacenar una fecha como UNIX timestamp en vez del formato ISO por defecto. Para esos casos hay atributos que permiten conectar converters propios.
En System.Text.Json:
public class UnixDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()).UtcDateTime;
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteNumberValue(new DateTimeOffset(value).ToUnixTimeSeconds());
}
public class LogEntry
{
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime EventTime { get; set; }
}
Ahora, al serializar, el campo EventTime se convertirá en un número en vez de una cadena con la fecha.
En Newtonsoft.Json:
public class BoolToYesNoConverter : JsonConverter<bool>
{
public override void WriteJson(JsonWriter writer, bool value, JsonSerializer serializer)
=> writer.WriteValue(value ? "yes" : "no");
public override bool ReadJson(JsonReader reader, Type objectType, bool existingValue, bool hasExistingValue, JsonSerializer serializer)
=> (string)reader.Value == "yes";
}
public class Answer
{
[JsonConverter(typeof(BoolToYesNoConverter))]
public bool IsCorrect { get; set; }
}
Ahora el booleano se serializa como "yes" o "no", y al deserializar vuelve a true o false.
7. Particularidades y trampas
Cuando añades atributos recuerda: distintos serializers usan distintos atributos. Si trabajas con JSON y con XML (o, en el peor de los casos, simultáneamente con Newtonsoft.Json y System.Text.Json), no olvides poner ambos atributos necesarios, si no tendrás sorpresas.
El nombre por defecto de la propiedad lo tomará el serializer, a menos que lo sobrescribas con un atributo.
Ten cuidado con la herencia: las clases hijas heredan campos/propiedades públicas y, si en la clase base había un atributo, se aplicará también en la clase derivada. Esto sorprende a menudo, pero es intencional.
Error típico: Suele pasar que los desarrolladores por accidente marcan un campo que debe entrar en la serialización con JsonIgnore (o al revés — se olvidan de ignorar datos sensibles). O, por ejemplo, ponen XmlElement en un campo privado — y luego se preguntan por qué el serializer no lo ve (XmlSerializer solo procesa miembros public!).
GO TO FULL VERSION