CodeGym /Kursy /C# SELF /Odczyt i zapis XML: XmlSer...

Odczyt i zapis XML: XmlSerializer

C# SELF
Poziom 48, Lekcja 0
Dostępny

1. Wprowadzenie

XML (eXtensible Markup Language) — rozszerzalny język znaczników, popularny format do przechowywania i wymiany złożonych, strukturalnych danych. W przeciwieństwie do JSON XML nie jest tak zwięzły, ale nadal jest szeroko używany w bankowości, wymianie między serwisami państwowymi, dokumentach i integracjach z Microsoft Dynamics 365, SAP, Oracle etc Niektóre dane w Windows i .NET (np. pliki konfiguracyjne) dalej bywają przechowywane właśnie w formacie XML.

W formie XML przypomina HTML, tylko bez stałego zestawu tagów typu <body> czy <div> — sam definiujesz strukturę elementów. Przykład:

<Person>
  <FirstName>Iwan</FirstName>
  <LastName>Pietrow</LastName>
  <Age>30</Age>
</Person>

Podstawy XML i dopasowanie do obiektów C#

W odróżnieniu od JSON, w XML nie ma specjalnych typów dla tablic czy wartości boolowskich — wszystko reprezentowane jest elementami i atrybutami. Struktury-listy realizowane są jako powtarzające się elementy o tej samej nazwie.

Aby wygodnie pracować z XML w C#, używa się mechanizmu serializacji/deserializacji przez XmlSerializer.

Serializacja — przekształcenie obiektu C# do ciągu XML. Deserializacja — odwrotny proces: odczytanie obiektu C# z XML.

To działa "od ręki", jeśli twoje klasy spełniają pewne wymagania — niżej je omówimy.

Po co używać XmlSerializer?

XmlSerializer — standardowe narzędzie .NET (przestrzeń nazw System.Xml.Serialization) do konwersji obiektów na XML i z powrotem. Jest proste, typowo-bezpieczne i nadaje się do importu/eksportu danych, konfigów, wymiany między systemami i integracji z serwisami państwowymi.

2. Przykład: serializacja obiektu

Mamy model użytkownika:

public class User
{
    public string FirstName { get; set; }
    public string LastName  { get; set; }
    public int Age          { get; set; }
}

Serializujemy obiekt do XML:

using System.Xml.Serialization;

User user = new User { FirstName = "Iwan", LastName = "Pietrow", Age = 30 };
// Do serializacji potrzebny jest typ
XmlSerializer serializer = new XmlSerializer(typeof(User));

// Zapisujemy XML do pliku
using FileStream fs = new FileStream("user.xml", FileMode.Create);
serializer.Serialize(fs, user);

Co się dzieje?

- Tworzymy obiekt klasy User.
- Tworzymy egzemplarz XmlSerializer dla typu User.
- Otwieramy plik przez FileStream.
- Wywołujemy metodę Serialize(), która zamienia obiekt na XML i zapisuje go do pliku.

Zawartość pliku user.xml:

<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <FirstName>Iwan</FirstName>
  <LastName>Pietrow</LastName>
  <Age>30</Age>
</User>

Tak prosto — minimum kodu i gotowy XML.

3. Deserializacja: czytamy XML z powrotem do obiektu

Czytamy stworzony XML i odtwarzamy obiekt C#:

// Otwieramy plik XML
using FileStream fs = new FileStream("user.xml", FileMode.Open);
User? loadedUser = serializer.Deserialize(fs) as User;
Console.WriteLine($"{loadedUser?.FirstName} {loadedUser?.LastName}, wiek: {loadedUser?.Age}");

Metoda Deserialize() zwraca object, więc robimy rzutowanie na User (przez as). Jeśli plik jest poprawny i odpowiada modelowi — obiekt zostanie w pełni odtworzony.

4. Jak działa serializacja: typowe cechy

Serializacja do XML wymaga przestrzegania zasad:

• Klasa do serializacji powinna mieć publiczny konstruktor domyślny (public bez parametrów).

• Serializowane właściwości — publiczne: potrzebny jest zarówno getter, jak i setter. Prywatne pola są ignorowane.

• Nie działają właściwości tylko do odczytu, a także właściwości statyczne i abstrakcyjne.

Przy złamaniu zasad otrzymasz InvalidOperationException z komunikatem typu "XmlSerializer cannot serialize this type".

5. Serializacja kolekcji: tablice i listy

Jeśli trzeba zserializować kilku użytkowników — używamy klasy opakowującej:

public class UserList
{
    public List<User> Users { get; set; } = new List<User>();
}

Utwórzmy kolekcję i zserializujmy:

UserList users = new UserList
{
    Users = new List<User>
    {
        new User { FirstName = "Iwan", LastName = "Pietrow", Age = 30 },
        new User { FirstName = "Maria", LastName = "Iwanowa", Age = 25 }
    }
};

XmlSerializer serializer = new XmlSerializer(typeof(UserList));
using FileStream fs = new FileStream("users.xml", FileMode.Create);
serializer.Serialize(fs, users);

Otrzymamy XML:

<?xml version="1.0"?>
<UserList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Users>
    <User>
      <FirstName>Iwan</FirstName>
      <LastName>Pietrow</LastName>
      <Age>30</Age>
    </User>
    <User>
      <FirstName>Maria</FirstName>
      <LastName>Iwanowa</LastName>
      <Age>25</Age>
    </User>
  </Users>
</UserList>

Czy można zserializować po prostu List<User>? Można, ale element root będzie nazywał się ArrayOfUser, co nie zawsze pasuje do integracji.

6. Atrybuty do sterowania serializacją

Przez atrybuty możesz sterować formatem XML: zmieniać nazwy elementów, serializować jako atrybuty, ignorować pola etc Przydatne atrybuty: [XmlElement], [XmlAttribute], [XmlArray], [XmlIgnore].

Serializacja jako atrybuty

Zapiszemy imię i nazwisko jako atrybuty, a wiek jako element:

public class User
{
    [XmlAttribute]
    public string FirstName { get; set; }

    [XmlAttribute]
    public string LastName { get; set; }

    [XmlElement]
    public int Age { get; set; }
}

Wynik:

<User FirstName="Iwan" LastName="Pietrow">
  <Age>30</Age>
</User>

Zarządzanie nazwami elementów

Zmodyfikujemy tagi na bardziej czytelne dla człowieka:

public class User
{
    [XmlElement("Imię")]
    public string FirstName { get; set; }

    [XmlElement("Nazwisko")]
    public string LastName { get; set; }

    [XmlElement("Wiek")]
    public int Age { get; set; }
}

Resultujący XML:

<User>
  <Imię>Iwan</Imię>
  <Nazwisko>Pietrow</Nazwisko>
  <Wiek>30</Wiek>
</User>

Ignorowanie właściwości

Jeśli właściwości nie trzeba serializować — oznacz ją [XmlIgnore]:

public class User
{
    public string FirstName { get; set; }

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

7. Serializacja hierarchii i obiektów zagnieżdżonych

Zagnieżdżone klasy łatwo stają się zagnieżdżonymi elementami XML:

public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

public class User
{
    public string FirstName { get; set; }
    public Address Address { get; set; }
}
var user = new User
{
    FirstName = "Anna",
    Address = new Address { City = "NeonCity", Street = "Elm" }
};

XmlSerializer serializer = new XmlSerializer(typeof(User));
using var fs = new FileStream("user_with_address.xml", FileMode.Create);
serializer.Serialize(fs, user);

XML:

<User>
  <FirstName>Anna</FirstName>
  <Address>
    <City>NeonCity</City>
    <Street>Elm</Street>
  </Address>
</User>

8. Serializacja do/z ciągu (bez plików)

Serializacja do ciągu

using System.Text;

var serializer = new XmlSerializer(typeof(User));
using var stringWriter = new StringWriter();

serializer.Serialize(stringWriter, user);
string xml = stringWriter.ToString();
Console.WriteLine(xml);

Deserializacja z ciągu

var xml = "<User><FirstName>Anna</FirstName></User>";
using var stringReader = new StringReader(xml);
User user = (User)serializer.Deserialize(stringReader);
Console.WriteLine(user.FirstName);

9. Przykład: eksport i import danych aplikacji

Eksport/import listy użytkowników do pliku XML:

public static void ExportUsers(UserList users, string path)
{
    var serializer = new XmlSerializer(typeof(UserList));
    using var fs = new FileStream(path, FileMode.Create);
    serializer.Serialize(fs, users);
}

public static UserList ImportUsers(string path)
{
    var serializer = new XmlSerializer(typeof(UserList));
    using var fs = new FileStream(path, FileMode.Open);
    return (UserList)serializer.Deserialize(fs);
}

Zastosowanie: kopia zapasowa, wymiana z innymi serwisami, integracje. Na rozmowach rekrutacyjnych pytania o XmlSerializer pojawiają się częściej, niż się wydaje — szczególnie w firmach pracujących z serwisami państwowymi i bankami.

10. Typowe błędy i cechy działania

Jeśli pole nie jest public, nie zostanie zserializowane.

Właściwości tylko do odczytu są ignorowane.

Właściwości o typie interfejsu lub abstrakcyjnej klasy nie są wspierane przez serializator.

Typ DateTime jest serializowany w formacie XSD (patrz dokumentację).

Kolekcje typu tablice i List<T> działają przewidywalnie, a z ObservableCollection<T> mogą być niespodzianki u systemów zewnętrznych.

Niuanse kodowań: przy zapisie przez StreamWriter bez jawnego ustawienia kodowania wynik może być w UTF-16; dla kompatybilności częściej oczekuje się UTF-8 — ustawiajcie kodowanie jawnie.

2
Zadanie
C# SELF, poziom 48, lekcja 0
Niedostępne
Serializacja listy obiektów
Serializacja listy obiektów
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION