JavaRush /Курси /C# SELF /Керування серіалізацією колекцій

Керування серіалізацією колекцій

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

1. Вступ

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

На щастя, System.Text.Json пропонує прості й прозорі засоби керування серіалізацією за допомогою атрибутів, які можна застосовувати як до колекцій, так і до окремих елементів. У цьому розділі продовжимо розвивати нашу бібліотечну модель, щоб побачити, як це працює на практиці.

2. Виключення колекційних властивостей: [JsonIgnore]

Почнемо з простого. Інколи у вашому класі є колекція, яку серіалізувати не можна — наприклад, це тимчасові, кешовані або чутливі дані. Що робити? Звісно ж, [JsonIgnore]!

Уявімо, що в нас є клас Library, до якого ми додали властивість List<Book> Cache, яку використовуємо лише для швидкого доступу:

using System.Text.Json.Serialization;

public class Library
{
    public string Name { get; set; }

    public List<Book> Books { get; set; }

    [JsonIgnore]
    public List<Book> Cache { get; set; } // Не серіалізується!
}
// Приклад використання:
var library = new Library
{
    Name = "Головна бібліотека",
    Books = new List<Book>
    {
        new Book { Title = "Чарівна долина", Author = new Author { Name = "Туве Янссон", BirthYear = 1914 } }
    },
    Cache = new List<Book>
    {
        new Book { Title = "Володар мух", Author = new Author { Name = "Вільям Голдінг", BirthYear = 1911 } }
    }
};

string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json); // У JSON немає властивості Cache!

Результат серіалізації буде приблизно такий:

{
  "Name": "Головна бібліотека",
  "Books": [
    {
      "Title": "Чарівна долина",
      "Author": {
        "Name": "Туве Янссон",
        "BirthYear": 1914
      }
    }
  ]
}

Бачите? Жодних «кешів» назовні. Усе, що під [JsonIgnore], — сховане й надійне, як пароль від Wi‑Fi у вашій голові.

3. Перейменування колекцій з [JsonPropertyName]

Часто ви працюєте з такими API, де очікують, наприклад, "items" замість "Books"? Або навпаки: ви не хочете перейменовувати поле в C# (щоб не плутатися), але в JSON воно має «звучати» інакше?

Ось як це робиться:

using System.Text.Json.Serialization;

public class Library
{
    public string Name { get; set; }

    [JsonPropertyName("items")]
    public List<Book> Books { get; set; }

    [JsonIgnore]
    public List<Book> Cache { get; set; }
}
// Серіалізація:
var library = new Library { Name = "Філія №1", Books = new List<Book>() };
string json = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);

Виведення:

{
  "Name": "Філія №1",
  "items": []
}

Зверніть увагу, що десеріалізація теж коректно зіставить поле items із JSON із Books у C# — «магія» працює в обидва боки.

4. Керування серіалізацією колекцій і їхніх елементів

Для цього вам знадобиться JsonIgnoreCondition.WhenWritingNull та/або nullable.

Буває, що колекція — це просто необов’язкова властивість. Наприклад, у щойно створеної бібліотеки ще немає книжок. Якщо ви не хочете, щоб у JSON з’являлася властивість books: null, можна керувати цим через опції:

var library = new Library { Name = "Пуста бібліотека" };
// Books не ініціалізована = null

var options = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    WriteIndented = true
};

string json = JsonSerializer.Serialize(library, options);
Console.WriteLine(json);

Результат:

{
  "Name": "Пуста бібліотека"
}

А якщо у вас є порожній список (але не null), то серіалізатор поверне "books": []. Це важлива різниця, адже інколи треба навмисно приховати властивість, якщо вона null, а не порожній список.

5. Атрибут [JsonIgnore] на властивостях елементів

Атрибути серіалізації працюють і всередині елементів колекції. Можете приховати окремі властивості у кожного об’єкта в списку.

public class Book
{
    public string Title { get; set; }
    public Author Author { get; set; }

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

Тепер під час серіалізації книги з колекції Books властивості InternalCode у JSON не буде.

6. Адресація колекцій через «індекси» або вкладені структури

Іноді доводиться серіалізувати колекції не просто як масиви, а, наприклад, як «мапи» (dictionary) — якщо кожна книжка має унікальний ідентифікатор. У такому разі — без хитрих атрибутів для елементів, але за допомогою стандартних засобів — можна оголосити властивість-словник:

public class Library
{
    [JsonPropertyName("catalog")]
    public Dictionary<string, Book> BookCatalog { get; set; }
}

Під час серіалізації словник перетвориться на об’єкт із парами ключ — значення:

var library = new Library
{
    BookCatalog = new Dictionary<string, Book>
    {
        ["978-5-699-12345-6"] = new Book { Title = "Словник", Author = new Author { Name = "Невідомий", BirthYear = 2000 } }
    }
};

JSON:

{
  "catalog": {
    "978-5-699-12345-6": {
      "Title": "Словник",
      "Author": {
        "Name": "Невідомий",
        "BirthYear": 2000
      }
    }
  }
}

Таке подання зручне для передачі через API, де важливо зберігати зв’язок між ключем і об’єктом.

7. Помилки й труднощі при керуванні серіалізацією колекцій

Якщо спробувати серіалізувати колекцію, у якій не всі елементи коректно ініціалізовані (наприклад, у списку є null), то за замовчуванням System.Text.Json запише такі елементи як null у масиві.

Навіть якщо встановлено DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, елементи‑null усередині масиву залишаться — правило стосується властивостей об’єкта, а не вмісту колекцій. Щоб цього уникнути, попередньо приберіть такі елементи з колекції: RemoveAll(b => b == null).

Часта плутанина під час десеріалізації — невідповідність назв. Якщо забути вказати [JsonPropertyName], клас очікуватиме властивість Books, а ви надішлете JSON з items: у результаті колекція не заповниться та залишиться порожньою. Завжди перевіряйте коректність назв!

8. Таблиця: де застосовні основні атрибути

Атрибут Чи можна застосовувати до колекцій? Чи можна застосовувати до елементів колекцій? Приклади використання
[JsonIgnore]
так так Приховати список або властивість усередині Book
[JsonPropertyName]
так так Перейменувати Books → items або Title → name
[JsonInclude]
так так Додати приватні властивості до серіалізації
[JsonConverter]
так так Призначити спеціальний конвертер для списку

9. Схема серіалізації колекцій з атрибутами


+-------------+
|   Library   |
+-------------+
  | Name           -- серіалізується як "Name"
  | Books          -- [JsonPropertyName("items")], серіалізується як "items": [...]
  | Cache          -- [JsonIgnore], не серіалізується
  | BookCatalog    -- [JsonPropertyName("catalog")], серіалізується як "catalog": {...}
JSON-результат приблизно такий:
{
  "Name": "Міська бібліотека",
  "items": [
    {
      "Title": "1984",
      "Author": {
        "Name": "Джордж Орвелл",
        "BirthYear": 1903
      }
    },
    {
      "Title": "Великі сподівання",
      "Author": {
        "Name": "Чарлз Діккенс",
        "BirthYear": 1812
      }
    }
  ],
  "catalog": {
    "978-1234567890": {
      "Title": "Поклик Ктулху",
      "Author": {
        "Name": "Говард Філліпс Лавкрафт",
        "BirthYear": 1890
      }
    }
  }
}

10. Практичне значення й нюанси на співбесідах і в реальних проєктах

У «бойових» умовах завжди доводиться враховувати контракт зовнішнього API та вимоги до серіалізації. Треба вміти «ховати» внутрішні колекції, дотримуватися регістру й стилю назв, інколи навіть динамічно змінювати схему серіалізації залежно від версії клієнта.

Типові запитання на співбесідах:

  • Як серіалізувати лише частину даних?
  • Як зробити, щоб властивість колекції не потрапила в JSON?
  • Як зіставити імена властивостей у C# і JSON, якщо вони різняться?
  • Чи можна приховати окремі елементи всередині колекції від серіалізації (наприклад, конфіденційну інформацію)?

Відповіді зводяться до грамотного використання атрибутів і параметрів серіалізації: [JsonIgnore], [JsonPropertyName], опцій JsonSerializerOptions і продуманої роботи зі вмістом колекцій.

1
Опитування
Серіалізація колекцій, рівень 46, лекція 4
Недоступний
Серіалізація колекцій
Серіалізація вкладених і ієрархічних об'єктів
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ