JavaRush /Курси /C# SELF /Серіалізація простих колекцій:

Серіалізація простих колекцій: List<T>, T[]

C# SELF
Рівень 46 , Лекція 0
Відкрита

1. Вступ

Ми з вами вже встигли попрацювати з колекціями. Без них нікуди — ні в програмуванні, ні в житті. Уявіть, що ви складаєте список справ на день: купити продукти, зателефонувати лікарю, забрати замовлення. Навряд чи ви заводитимете окремий блокнот для кожної справи. Набагато простіше й логічніше занотувати всі завдання в один список.

Так само й у програмуванні: коли об’єктів стає багато — користувачів, замовлень, повідомлень — ми не завжди створюємо для кожного окрему змінну. Замість цього використовуємо колекції: списки, словники, множини. Це дає змогу зручно зберігати, перебирати й обробляти одразу цілі групи даних.

Сценарії з реального життя:

  • Зберігання та обмін даними між застосунками: ваша колекція книжок може мігрувати з одного застосунку до іншого.
  • Кешування наборів даних на диску.
  • Передача даних мережею (frontend ↔ backend).
  • Імпорт/експорт інформації (наприклад, ви вирішили зробити власний експорт книжок у JSON!).

На співбесіді: «Як би ви серіалізували й зберегли список замовлень?» — буде чудово, якщо згадаєте не тільки один об’єкт, а й серіалізацію колекцій.

2. Як працюють колекції під час серіалізації

JSON і колекції: коротка історія кохання

Коли ви серіалізуєте звичайний об’єкт, він перетворюється на JSON-об’єкт виду { "field": value }. А якщо серіалізувати список або масив — отримуємо JSON-масив [ ... ].

Візуально:

C# JSON
List<int> { 1, 2, 3 }
[1, 2, 3]
Book[]
[{"Title":"A","Author":"B"}, ...]
List<Book>
[{"Title":"A"}, {"Title":"B"}]

Головна «магія»: достатньо викликати JsonSerializer.Serialize() для колекції — і вона автоматично перетворюється на масив. У зворотний бік — Deserialize<List<T>>() — і «магія» спрацьовує знову.

Відповідність між колекціями C# і JSON-масивами

Тип колекції в C# Приклад JSON
Book[]
new Book[] { ... }
[ { ... }, { ... } ]
List<Book>
new List<Book> { ... }
[ { ... }, { ... } ]
int[]
new int[] { 1, 2, 3 }
[1, 2, 3]
List<string>
new List<string> { "a", "b" }
["a", "b"]
List<List<Book>>
new List<List<Book>> { ... }
[ [ { ... } ], [ { ... } ] ]

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> з тими самими значеннями

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>), але інколи можуть бути нюанси, якщо, наприклад, потрібно десеріалізувати масив рядків в об’єкт, а не в колекцію.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ