CodeGym /Cursos /C# SELF /Configuración de la serialización XML

Configuración de la serialización XML

C# SELF
Nivel 48, Lección 1
Disponible

1. Introducción

Ya sabes que .NET puede serializar y deserializar objetos a XML por defecto, basándose en campos y propiedades públicas. Pero cuando quieres un XML bonito, compatible con programas/estándares externos, o cuando no quieres incluir ciertos detalles internos en el XML, necesitas ajustes finos.

En C# existen atributos de serialización — etiquetas especiales que ponemos en clases, propiedades o campos. Gracias a estos atributos podemos controlar prácticamente todo: qué campos entran al XML, cuáles se ignoran, cómo se llamará un elemento, si será un atributo, si tendrá una estructura compleja e incluso el orden de los elementos.

Imagina que la serialización por defecto es como rellenar un formulario automáticamente: nombre-apellido-otrocampo en sus casillas, el resto — como salga. Con los atributos eres como un funcionario super-ordenado: decides qué cadena va dónde, qué omitir, qué resaltar en rojo y qué esconder en una sección secreta.

Mapeo entre objeto y XML

Clase/Propiedad Atributo Resultado en XML
[XmlRoot("Persons")]
Clase UserList
<Persons>...</Persons>
List<User> Users
[XmlElement("Person")]
<Person>...</Person>
int Id
[XmlAttribute("id")]
id="..." (atributo)
string Name
[XmlElement("FullName")]
<FullName>...</FullName>
string[] Emails
[XmlArray("EMails")][XmlArrayItem("Email")]
<EMails><Email>...</Email></EMails>
DateTime RegisteredAt
[XmlIgnore] (ver RegisteredAtString) -
string RegisteredAtString
[XmlAttribute("registered")]
registered="..."
Address[] Addresses
[XmlArray("UserAddresses")][XmlArrayItem("Address")]
<UserAddresses>...</UserAddresses>

2. Repaso de los atributos principales de serialización XML

Vamos a repasar los atributos más populares que la gente de .NET ha usado durante generaciones.

Atributo [XmlElement] — Nombre del elemento

Cambia el nombre del elemento en el XML o marca una propiedad/campo como elemento XML.

public class Person
{
    [XmlElement("FullName")]
    public string Name { get; set; }
}

XML:

<Person>
  <FullName>John Doe</FullName>
</Person>

Si no especificas el atributo — por defecto el nombre del elemento coincide con el nombre de la propiedad/campo.

Atributo [XmlAttribute] — Serializar como atributo XML

Permite serializar una propiedad/campo como atributo XML en vez de elemento.

public class Person
{
    [XmlAttribute("id")]
    public int Id { get; set; }
}

XML:

<Person id="123"></Person>

En APIs XML reales a menudo se usan atributos para identificadores, fechas, flags, etc.

Atributo [XmlIgnore] — Omitir campo/propiedad

El sueño de cualquier paranoico: excluir completamente una propiedad del proceso de serialización y deserialización. Todo lo marcado con este atributo simplemente no aparecerá en el XML final.

public class Person
{
    [XmlIgnore]
    public string InternalNote { get; set; }
}

XML: (la propiedad InternalNote no aparece)

Atributos [XmlArray] y [XmlArrayItem] — control de colecciones

Atributos especiales para arrays y colecciones. [XmlArray] define el nombre del envoltorio externo, y [XmlArrayItem] — el nombre de cada elemento.

public class Person
{
    [XmlArray("Phones")]
    [XmlArrayItem("Phone")]
    public string[] PhoneNumbers { get; set; }
}

XML:

<Person>
  <Phones>
    <Phone>+12951234567</Phone>
    <Phone>+12876543210</Phone>
  </Phones>
</Person>

Atributo [XmlRoot] — Nombre del elemento raíz

Marca la clase para cambiar el nombre del elemento raíz en el XML.

[XmlRoot("User")]
public class Person
{
    public string Name { get; set; }
}

XML:

<User>
  <Name>Anna</Name>
</User>

Atributo [XmlText] — Serializar como contenido de texto del elemento

A veces quieres (o la especificación lo requiere) que el valor de la propiedad sea simplemente el texto dentro del elemento, no un subelemento o un atributo.

public class Note
{
    [XmlText]
    public string Content { get; set; }
}

XML:

<Note>¡Llama a la abuela!</Note>

Atributos [XmlNamespaceDeclarations], [XmlElement(Type = ...)]

Para escenarios más avanzados (por ejemplo, serialización con namespaces, soporte de herencia, etc.), hay atributos más específicos. Revisa la documentación oficial sobre atributos de XML-serialization para la lista completa.

3. Práctica: mejorando la serialización con un ejemplo real

Vamos paso a paso a mejorar nuestra app de aprendizaje hasta una "serialización XML de primera clase", para que agrade incluso al inspector XML más quisquilloso.

Clase base sin atributos

Supongamos que tenemos esta clase de usuario:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string[] Emails { get; set; }
    public DateTime RegisteredAt { get; set; }
    public string Password { get; set; }
}

La serialización por defecto dará algo así (y además la fecha la pondrá en su propio formato):

<User>
  <Id>42</Id>
  <Name>John Doe</Name>
  <Emails>
    <string>user@mail.com</string>
    <string>admin@site.org</string>
  </Emails>
  <RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
  <Password>qwerty</Password>
</User>

Qué está mal:

  • Mejor no serializar la contraseña en absoluto.
  • Queremos la fecha como atributo y en formato humano.
  • Queremos los Emails en una forma bonita.
  • Queremos que el elemento raíz sea <Person>.

Añadimos configuraciones con atributos

[XmlRoot("Person")]
public class User
{
    [XmlAttribute("id")]
    public int Id { get; set; }

    [XmlElement("FullName")]
    public string Name { get; set; }

    [XmlArray("EMails")]
    [XmlArrayItem("Email")]
    public string[] Emails { get; set; }

    [XmlAttribute("registered")]
    public string RegisteredAtString
    {
        get => RegisteredAt.ToString("yyyy-MM-dd");
        set => RegisteredAt = DateTime.Parse(value);
    }

    [XmlIgnore]
    public string Password { get; set; }

    [XmlIgnore]
    public DateTime RegisteredAt { get; set; }
}

Explicación de las sutilezas:

  • Usamos [XmlAttribute("registered")] para serializar la fecha como atributo.
  • Para serializar no el DateTime en sí, sino su representación en string, añadimos una propiedad separada RegisteredAtString, y escondemos el campo real con [XmlIgnore].
  • La contraseña la escondemos con [XmlIgnore].
  • El array de emails está configurado para que el tag externo sea <EMails>, y cada email — <Email>.

Resultado:

<Person id="42" registered="2024-07-01">
  <FullName>John Doe</FullName>
  <EMails>
    <Email>user@mail.com</Email>
    <Email>admin@site.org</Email>
  </EMails>
</Person>

4. Trabajo con objetos anidados

XML brilla cuando hay objetos anidados (por ejemplo, un usuario y sus direcciones).

public class Address
{
    [XmlAttribute]
    public string City { get; set; }

    [XmlText]
    public string Details { get; set; }
}

public class User
{
    // ...otras propiedades, ver arriba...

    public Address[] Addresses { get; set; }
}

Añadimos configuración para el array de direcciones:

[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }

Entonces el XML quedará así:

<Person id="42" registered="2024-07-01">
  <FullName>John Doe</FullName>
  <EMails>
    <Email>user@mail.com</Email>
  </EMails>
  <UserAddresses>
    <Address City="Neon city">Castle St., 1</Address>
    <Address City="North Cave">Edge St., 12</Address>
  </UserAddresses>
</Person>

Se ve que en cada <Address> el city es un atributo, y la dirección en sí está como texto dentro del elemento.

5. Errores comunes y matices

Si serializas un objeto complejo y no ves los cambios en el XML que esperabas — probablemente olvidaste aplicar correctamente el atributo. A veces las propiedades/campos quedan sin marcar y .NET simplemente aplica el comportamiento por defecto.

Otra trampa es el nombre de la colección. Si una colección (por ejemplo, un array de strings) no tiene ni [XmlArray] ni [XmlArrayItem], los elementos se escribirán con la etiqueta string (como se vio en el ejemplo inicial). Para evitarlo, siempre especifica explícitamente los nombres de los tags con atributos.

Ten cuidado con propiedades "getter-only" (solo lectura): esas propiedades no se deserializan por defecto. Procura tener un set público.

Algunos tipos (por ejemplo, diccionarios o interfaces) no son serializables por el XmlSerializer estándar, o requieren un manejo especial.

2
Tarea
C# SELF, nivel 48, lección 1
Bloqueada
Serialización de una colección usando XmlArray y XmlArrayItem
Serialización de una colección usando XmlArray y XmlArrayItem
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION