CodeGym /Kursy /C# SELF /Konfiguracja serializacji XML

Konfiguracja serializacji XML

C# SELF
Poziom 48 , Lekcja 1
Dostępny

1. Wprowadzenie

Już wiesz, że .NET potrafi serializować i deserializować obiekty do XML domyślnie, opierając się na publicznych polach i właściwościach. Ale kiedy chcesz zrobić ładny XML, kompatybilny z cudzymi programami/standardami, albo gdy nie chcesz umieszczać w XML jakichś wewnętrznych detali, potrzebna jest drobna konfiguracja.

W C# do tego służą atrybuty serializacji — specjalne „etykiety”, którymi oznaczamy klasy, właściwości lub pola. Dzięki tym atrybutom możemy sterować dosłownie wszystkim: które pola trafią do XML, które będą ignorowane, jak element będzie się nazywał, czy stanie się atrybutem, czy ma mieć złożoną strukturę, a nawet jaki będzie porządek elementów!

Wyobraź sobie, że domyślna serializacja to jak automatyczne wypełnianie formularza: imię-nazwisko-środowisko w swoje pola, reszta — jak popadnie. A z atrybutami jesteś jak super-urzędnik: sam decydujesz, którą linię gdzie zapisać, co pominąć, co wyróżnić czerwonym, a co schować do sekcji tajnej.

Mapowanie obiektu na XML

Klasa/Właściwość Atrybut Rezultat w XML
[XmlRoot("Persons")]
Klasa UserList
<Persons>...</Persons>
List<User> Users
[XmlElement("Person")]
<Person>...</Person>
int Id
[XmlAttribute("id")]
id="..." (atrybut)
string Name
[XmlElement("FullName")]
<FullName>...</FullName>
string[] Emails
[XmlArray("EMails")][XmlArrayItem("Email")]
<EMails><Email>...</Email></EMails>
DateTime RegisteredAt
[XmlIgnore] (zob. RegisteredAtString) -
string RegisteredAtString
[XmlAttribute("registered")]
registered="..."
Address[] Addresses
[XmlArray("UserAddresses")][XmlArrayItem("Address")]
<UserAddresses>...</UserAddresses>

2. Przegląd podstawowych atrybutów serializacji XML

Przejdźmy po najpopularniejszych atrybutach, którymi użytkownicy .NET ozdabiali swoje klasy przez wiele pokoleń!

Atrybut [XmlElement] — Nazwa elementu

Zmieniaj nazwę elementu w XML lub oznacz właściwość/pole jako element XML.

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

XML:

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

Jeśli nie podasz atrybutu — domyślnie nazwa elementu będzie taka sama jak nazwa właściwości/pola.

Atrybut [XmlAttribute] — Serializacja do atrybutu XML

Pozwala serializować właściwość/pole jako atrybut XML, a nie jako element.

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

XML:

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

W realnych XML-API często używa się atrybutów dla identyfikatorów, dat, flag itp.

Atrybut [XmlIgnore] — Pomijanie pola/właściwości

Marzenie każdego paranoika: całkowicie wykluczyć właściwość z procesu serializacji i deserializacji! Wszystko, co oznaczone tym atrybutem, po prostu nie trafi do finalnego XML.

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

XML: (właściwości InternalNote tu nie ma i nie będzie)

Atrybuty [XmlArray] i [XmlArrayItem] — kontrola nad kolekcjami

Specjalne atrybuty dla tablic i kolekcji. [XmlArray] ustala nazwę zewnętrznego wrappera-tablicy, a [XmlArrayItem] — nazwę pojedynczego elementu.

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

XML:

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

Atrybut [XmlRoot] — Nazwa elementu root

Oznaczasz samą klasę, żeby zmienić nazwę korzeniowego elementu w XML.

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

XML:

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

Atrybut [XmlText] — Serializacja jako zawartość tekstowa elementu

Czasem chcesz (albo wymaga specyfikacja), żeby wartość właściwości była po prostu tekstem wewnątrz elementu, a nie jego pod-elementem czy atrybutem.

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

XML:

<Note>Zadzwoń do babci!</Note>

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

Do bardziej zaawansowanych scenariuszy (np. serializacja z namespace'ami, wsparcie dziedziczenia itp.) są też bardziej specyficzne atrybuty. Koniecznie zerknij na oficjalną dokumentację po XML-serializacji dla pełnej listy.

3. Praktyka: poprawiamy serializację na realnym przykładzie

Zróbmy krok po kroku nasze edukacyjne appkę do „XML-serializacji premium”, żeby zadowolić nawet najbardziej wymagającego XML-inspektora.

Klasa bazowa bez atrybutów

Załóżmy, że mamy taką klasę użytkownika:

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; }
}

Domyślna serializacja da mniej więcej taki XML (i datę zapisze w swoim formacie):

<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>

Co jest nie tak:

  • Hasło lepiej w ogóle nie serializować.
  • Chcemy datę jako atrybut i w czytelnym formacie.
  • Chcemy Emails w ładnej formie.
  • Chcemy korzeń <Person>.

Dodajemy ustawienia przez atrybuty

[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; }
}

Wyjaśnienie niuansów:

  • Używamy [XmlAttribute("registered")] żeby serializować datę jako atrybut.
  • Aby serializować nie sam DateTime, a jego reprezentację tekstową, dodajemy osobne pole RegisteredAtString, a rzeczywiste pole ukrywamy przez [XmlIgnore].
  • Hasło ukrywamy za pomocą [XmlIgnore].
  • Tablica emaili jest skonfigurowana tak, że zewnętrzny tag to <EMails>, a każdy email to <Email>.

Rezultat:

<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. Praca z zagnieżdżonymi obiektami

XML pokazuje swoją moc najbardziej, gdy mamy zagnieżdżone obiekty (np. użytkownik i jego adresy).

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

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

public class User
{
    // ...inne właściwości, patrz wyżej...

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

Dodajmy konfigurację dla tablicy adresów:

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

Wówczas XML będzie wyglądać tak:

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

Widać, że dla każdego <Address> city to atrybut, a sam adres jest tekstem wewnątrz elementu.

5. Częste błędy i niuanse

Jeśli serializujesz złożony obiekt i nie widzisz zmian w XML, których się spodziewałeś — najprawdopodobniej zapomniałeś poprawnie ustawić atrybut. Czasem właściwości/pola zostają bez oznaczeń. Wtedy .NET po prostu wybiera domyślne zachowanie.

Kolejna pułapka — nazwa kolekcji. Jeśli kolekcja (np. tablica stringów) nie jest oznaczona ani [XmlArray], ani [XmlArrayItem], to elementy zostaną zapisane z tagiem string (jak w początkowym przykładzie powyżej). Aby tego uniknąć, zawsze jawnie ustawiaj nazwy tagów przez atrybuty.

Uważaj na typy właściwości „getter-only” (tylko do odczytu) — takie właściwości domyślnie się nie deserializują. Staraj się mieć publiczny set.

Niektóre typy (np. słowniki albo interfejsy) nie są serializowane przez standardowy XmlSerializer, albo wymagają osobnego traktowania.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION