1. Wprowadzenie
Przypomnienie: Serializacja — to sposób zapisania stanu obiektu w postaci stringa, pliku lub strumienia, żeby później można było odtworzyć ten obiekt (deserializować) — i znów masz obiekt z tymi samymi danymi.
Typowa sytuacja życiowa:
1) Masz obiekt, np. użytkownika z imieniem i wiekiem.
2) Trzeba to zapisać — przesłać po sieci, zapisać do pliku lub np. do formatu JSON.
3) Potem dane z łańcucha/ pliku odczytujesz z powrotem — i znowu masz obiekt!
W C# da się to zrobić dosłownie w kilku linijkach. Zobaczmy, jak dokładnie.
Nasza aplikacja
Tworzymy małą aplikację "Menedżer kontaktów". Niech mamy klasę Person, którą chcemy serializować.
public class Person
{
// Publiczne właściwości są wymagane do serializacji!
public string Name { get; set; }
public int Age { get; set; }
// Konstruktor bez parametrów jest wymagany dla XmlSerializer
public Person() { }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Jeżeli nie znasz jeszcze takiego składnika, nie martw się — dalej w kursie omówimy klasy i właściwości szczegółowo. Na razie potraktuj to jako model danych do eksperymentów. Zwróć uwagę: publiczne właściwości Name i Age są istotne dla serializacji, a dla XmlSerializer potrzebny jest publiczny konstruktor bez parametrów.
2. Serializacja do JSON za pomocą System.Text.Json
Najprostszy przykład
Obiecaliśmy — pokazujemy! Tak wygląda serializacja do JSON:
using System;
using System.Text.Json;
Person person = new Person("Alisa", 28);
// Serializacja: zamieniamy obiekt na string JSON
string json = JsonSerializer.Serialize(person);
Console.WriteLine("JSON:");
Console.WriteLine(json);
Co pojawi się na ekranie?
JSON:
{"Name":"Alisa","Age":28}
Prosto: obiekt → JSON. Oto magia!
Deserializacja — odtwarzamy wszystko z powrotem
Teraz wykonamy operację odwrotną — z JSON-łańcucha odtworzymy obiekt:
string json = "{\"Name\":\"Bob\",\"Age\":35}";
// Deserializacja: odtwarzamy obiekt typu Person z łańcucha JSON
Person restored = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine("Przywrócony obiekt:");
Console.WriteLine($"Imię: {restored.Name}, wiek: {restored.Age}");
Wynik:
Przywrócony obiekt:
Imię: Bob, wiek: 35
Zwróć uwagę: jeśli struktura danych się nie zgadza (np. brakuje jakiejś właściwości), odpowiednie pole pozostanie z wartością domyślną (null dla typów referencyjnych, 0 dla liczb itp.).
3. Serializacja/deserializacja do XML za pomocą XmlSerializer
Serializacja do XML — pierwszy rzut oka
using System;
using System.IO;
using System.Xml.Serialization;
Person person = new Person("Katya", 22);
// Tworzymy serializer dla typu Person
var serializer = new XmlSerializer(typeof(Person));
// Zapisujemy XML do pliku
using (var stream = new FileStream("person.xml", FileMode.Create))
{
serializer.Serialize(stream, person);
// Strumień zostanie automatycznie zamknięty na końcu using-bloku
}
Console.WriteLine("Plik XML utworzony!");
W pliku person.xml pojawi się coś w tym stylu:
<?xml version="1.0"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Katya</Name>
<Age>22</Age>
</Person>
Ważna uwaga:
Do serializacji do pliku używamy strumienia (Stream, np. FileStream). Jeśli chcesz serializować do łańcucha (np. do wysłania po sieci), użyj StringWriter:
var serializer = new XmlSerializer(typeof(Person));
using (var sw = new StringWriter())
{
serializer.Serialize(sw, person);
string xmlString = sw.ToString();
Console.WriteLine(xmlString);
}
Deserializacja z XML
using (var stream = new FileStream("person.xml", FileMode.Open))
{
// Nie zapomnij rzutować do odpowiedniego typu!
Person restored = (Person)serializer.Deserialize(stream);
Console.WriteLine($"Imię: {restored.Name}, wiek: {restored.Age}");
}
Jeszcze raz: XmlSerializer wymaga, żeby serializowana klasa miała publiczny konstruktor domyślny (bez parametrów).
4. Praca z plikami i łańcuchami: co wybrać?
W praktyce serializacja może iść bezpośrednio do łańcucha (do przesyłu po sieci/przechowywania w DB) lub do pliku (do długotrwałego przechowywania).
Serializacja do łańcucha (np. do przesłania przez API):
- JSON: używamy JsonSerializer.Serialize(obj).
- XML: przez StringWriter.
Serializacja do pliku (np. do eksportu/kopii zapasowej):
- JSON: zapisujemy string do pliku standardowymi metodami zapisu.
- XML: bezpośrednio piszemy do strumienia przy pomocy XmlSerializer.
Przykład: serializacja do pliku i z powrotem (JSON)
using System.IO;
using System.Text.Json;
Person person = new Person("Tom", 42);
// Serializacja do stringa
string json = JsonSerializer.Serialize(person);
// Zapis do pliku
File.WriteAllText("contact.json", json);
// Odczyt z pliku
string readJson = File.ReadAllText("contact.json");
Person restored = JsonSerializer.Deserialize<Person>(readJson);
Console.WriteLine($"{restored.Name}, wiek: {restored.Age}");
5. Porównanie JSON vs XML
Wizualizacja procesu
Aby się nie pogubić, oto schemat procesu serializacji i deserializacji:
flowchart LR
A[Obiekt] -- serializacja --> B[String/Plik/Strumień]
B -- deserializacja --> C[Obiekt]
A -. JSON lub XML .-> B
B -. JSON lub XML .-> C
Możesz sobie wyobrazić, że serializacja to "spakować walizkę", a deserializacja to "rozpakować" w domu.
Tabela porównawcza
| Funkcjonalność | JSON (System.Text.Json) | XML (XmlSerializer) |
|---|---|---|
| Odczyt/zapis do stringa | ++ | + |
| Odczyt/zapis do pliku | ++ | ++ |
| Wymaga konstruktora bez parametrów | Nie | Tak |
| Czytelność dla człowieka | ++ | + |
| Ścisły schemat danych (walidacja) | - | ++ |
| Elastyczność (atrybuty do customizacji) | + | ++ |
| Wydajność | ++ | + |
| Kompatybilność z internetem | ++ | + |
++ — bardzo dobrze, + — w porządku, - — nieobsługiwane
6. Jak dodać serializację do naszej aplikacji
Niech w "Menedżerze kontaktów" pojawi się możliwość zapisywania listy kontaktów do pliku i jej przywracania.
Kontekst: mamy listę kontaktów (List<Person>).
List<Person> contacts = new List<Person>
{
new Person("Vasya", 30),
new Person("Petya", 25),
new Person("Masha", 27)
};
// Zapisujemy wszystkie kontakty — serializujemy do JSON
string jsonContacts = JsonSerializer.Serialize(contacts);
File.WriteAllText("contacts.json", jsonContacts);
// Przywracamy listę kontaktów
string readContactsJson = File.ReadAllText("contacts.json");
List<Person> restored = JsonSerializer.Deserialize<List<Person>>(readContactsJson);
foreach (var c in restored)
{
Console.WriteLine($"{c.Name}, wiek: {c.Age}");
}
To samo da się zrobić z XML, ale tam są różnice. Przy serializacji kolekcji przez XmlSerializer zwykle opakowuje się listę w osobną klasę-kontener (element root) — wtedy łatwiej kontrolować format i nazwy elementów.
7. Typowe błędy przy serializacji i deserializacji
Błąd nr 1: niezgodność typów przy serializacji i deserializacji.
Zserializowałeś List<Person> — deserializować musisz też do List<Person>, a nie do List<object> czy IEnumerable<Person>. W przeciwnym razie dostaniesz błąd lub nieoczekiwane rezultaty.
Błąd nr 2: próba serializacji niepublicznych pól i właściwości.
Typowe serializery (JsonSerializer, XmlSerializer) pracują na publicznych elementach. Jeśli właściwość nie ma publicznego gettera/settera, wartość może nie trafić do JSON/XML, i będziesz się zastanawiać, dlaczego tam jest null.
Błąd nr 3: brak konstruktora bez parametrów.
Dla XmlSerializer publiczny konstruktor domyślny jest obowiązkowy. Jeśli go nie ma — będzie wyjątek przy deserializacji.
Błąd nr 4: błędne rozumienie referencji do obiektów.
Jeśli jeden obiekt zawiera referencję do innego, serializacja odbywa się "przez wartość" (jako obiekt zagnieżdżony). Referencje same w sobie nie są zapisywane, co może prowadzić do duplikacji danych lub problemów z cyklicznymi referencjami.
Błąd nr 5: próba mieszania formatów serializacji.
XML i JSON to różne formaty i różni serializerzy. Nie możesz podać JSON-łańcucha do XmlSerializer i odwrotnie — dostaniesz błąd.
GO TO FULL VERSION