1. Введение
Мы с вами уже успели поработать с коллекциями. Без них никуда ни в программировании, ни в жизни. Представьте, что вы составляете список дел на день: купить продукты, позвонить врачу, забрать заказ. Вряд ли вы будете заводить для каждого дела отдельный блокнот. Куда проще и логичнее записать все задачи в один список.
То же самое и в программировании: когда объектов становится много — пользователей, заказов, сообщений — мы не всегда создаём под каждый отдельную переменную. Вместо этого используем коллекции: списки, словари, множества. Это позволяет удобно хранить, перебирать и обрабатывать сразу целые группы данных.
Сценарии из реальной жизни:
- Хранение и обмен данными между приложениями: ваша коллекция книг может мигрировать из одной программы в другую.
- Кеширование наборов данных на диск.
- Передача данных по сети (frontend ↔ backend).
- Импорт/экспорт информации (например, вы решили сделать свой экспорт книг в JSON!).
На собеседовании: «А как бы вы сериализовали и сохранили список заказов?» — будет классно, если вы назовёте не только один объект, но и сериализацию коллекций!
2. Как работают коллекции при сериализации
JSON и коллекции: недолгая история любви
Когда вы сериализуете обычный объект, он превращается в JSON-объект вида { "field": value }. А вот если сериализовать список или массив, получится JSON-массив [ ... ].
Визуально:
| C# | JSON |
|---|---|
|
|
|
|
|
|
Главная магия: Вызываем JsonSerializer.Serialize() для коллекции — и он автоматом превращает её в массив! В обратную сторону Deserialize<List<T>>() — и волшебство срабатывает снова.
Соответствие между коллекциями C# и JSON-массивами
| Тип коллекции в C# | Пример | JSON |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. Пример: сериализация и десериализация массива книг
Давайте начнём с минимального примера. Возьмём наш класс Book, который мы уже писали в прошлых лекциях:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
Теперь создадим массив книг, сериализуем его, сохраним в файл и загрузим обратно.
using System;
using System.IO;
using System.Text.Json;
namespace LibraryApp
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
class Program
{
static void Main()
{
// Создадим массив книг
Book[] books = new Book[]
{
new Book { Title = "Три товарища", Author = "Эрих Мария Ремарк" },
new Book { Title = "Мастер и Маргарита", Author = "Михаил Булгаков" },
new Book { Title = "1984", Author = "Джордж Оруэлл" }
};
// Сериализация массива книг в строку
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(books, options);
Console.WriteLine("JSON массива книг:\n" + json);
// Запись в файл (синхронно)
File.WriteAllText("books.json", json);
// Чтение из файла (синхронно)
string jsonFromFile = File.ReadAllText("books.json");
// Десериализация обратно в массив
Book[]? booksFromFile = JsonSerializer.Deserialize<Book[]>(jsonFromFile);
// Выводим результат на консоль
Console.WriteLine("\nДесериализованные книги:");
if (booksFromFile != null)
{
foreach (var book in booksFromFile)
{
Console.WriteLine($"- {book.Title} (автор: {book.Author})");
}
}
}
}
}
Что здесь происходит?
- Мы создаём массив Book[], заполняем его тремя книгами.
- Через JsonSerializer.Serialize превращаем массив в красивую JSON-строку (опция WriteIndented делает форматирование читаемым).
- Записываем её в файл, читаем обратно и десериализуем — и вот, у нас снова есть массив книг!
- Проверяем, что все данные корректно восстановились.
Проверьте, что файл "books.json" появляется в папке с программой. Откройте его, там будет примерно такой JSON:
[
{
"Title": "Три товарища",
"Author": "Эрих Мария Ремарк"
},
{
"Title": "Мастер и Маргарита",
"Author": "Михаил Булгаков"
},
{
"Title": "1984",
"Author": "Джордж Оруэлл"
}
]
4. Пример: сериализация коллекции List<T>
Работа с List<Book> ничем не отличается. Сериализатор видит коллекцию и превращает её в JSON-массив.
List<Book> myBooks = new List<Book>
{
new Book { Title = "Преступление и наказание", Author = "Фёдор Достоевский" },
new Book { Title = "Война и мир", Author = "Лев Толстой" }
};
string jsonList = JsonSerializer.Serialize(myBooks, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonList);
// А десериализуем вот так:
List<Book>? loadedBooks = JsonSerializer.Deserialize<List<Book>>(jsonList);
// Проверяем, что всё вернулось!
foreach (var book in loadedBooks!)
{
Console.WriteLine($"{book.Title} ({book.Author})");
}
А можно ли сериализовать просто список целых чисел?
Конечно! Сериализация работает не только для ваших классов, но и для простых типов:
List<int> numbers = new List<int> { 10, 20, 30, 40 };
string jsonNums = JsonSerializer.Serialize(numbers); // результат: [10,20,30,40]
List<int>? loadedNums = JsonSerializer.Deserialize<List<int>>(jsonNums);
// loadedNums: List<int> c теми же значениями
5. Схема и аналогии: как сериализатор видит коллекции?
Чтобы лучше понять, как работает сериализация коллекций, взглянем на простую схему:
graph TD;
A[List[Book] в C#] -->|Serialize| B[JSON-массив в строке]
B -->|Write| C[Файл books.json]
C -->|Read| D[Строка JSON из файла]
D -->|Deserialize| E[List[Book] в C#]
Коротко описывая процесс:
- Коллекция или массив сериализуется как массив в JSON ([ ... ]).
- Порядок элементов сохраняется (если вы не вмешались специальными настройками).
- Каждый элемент коллекции сериализуется как свой объект.
6. Особенности сериализации коллекций: на что обратить внимание
1. null и пустые коллекции
Если коллекция null, сериализатор по умолчанию просто запишет null в JSON. Будьте аккуратны, если в вашей бизнес-логике пустая коллекция и null — не одно и то же!
Если коллекция пустая (new List<Book>()), JSON будет выглядеть как [] — пустой массив. Это удобно, когда надо явно показать отсутствие элементов.
2. Десериализация — порядок важен
Порядок элементов в массиве всегда сохраняется. То есть если сериализовали три книги в таком же порядке, такой же порядок и восстановится.
3. Коллекция с объектами разных типов?
System.Text.Json не поддерживает полиморфизм «из коробки». То есть если у вас, например, List<Animal> с собаками и котами (где каждый — производный класс), то восстановить «какой именно это тип» по умолчанию нельзя. До полиморфной сериализации мы ещё дойдём, но на обычных списках — всё просто.
7. Типичные ошибки и распространённые недоразумения
Ошибка №1: Сериализуем коллекцию, но забываем, что в ней могут быть объекты с приватными полями
JsonSerializer сериализует только публичные свойства (и только с геттерами/сеттерами). Если у вас класс вот такой:
public class User
{
public string Login { get; set; }
private string Password { get; set; } // Не будет сериализовано!
}
Пароль в JSON не попадёт, и это хорошо для безопасности. Но если вы хотели его сохранить — не забудьте сделать свойство публичным.
Ошибка №2: Сериализация коллекций с null-элементами
Если коллекция содержит null, например: new List<Book> { null, book2 }, то после сериализации на месте первого элемента будет null. При десериализации — точно так же! Пример такого JSON:
[null, { "Title": "Война и мир", "Author": "Лев Толстой" }]
На практике это бывает редко, но если вы сериализуете коллекцию, где могут быть "дырки" — учитывайте это в логике.
Ошибка №3: Не тот тип при десериализации
Частая опечатка: сериализовали список, а десериализуем в массив (или наоборот). Это работает, если типы совместимы (Book[] ↔ List<Book>), но иногда могут быть нюансы, если, например, вы захотели десериализовать массив строк в объект, а не в коллекцию.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ