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. Таблиця: де застосовні основні атрибути
| Атрибут | Чи можна застосовувати до колекцій? | Чи можна застосовувати до елементів колекцій? | Приклади використання |
|---|---|---|---|
|
так | так | Приховати список або властивість усередині Book |
|
так | так | Перейменувати Books → items або Title → name |
|
так | так | Додати приватні властивості до серіалізації |
|
так | так | Призначити спеціальний конвертер для списку |
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 і продуманої роботи зі вмістом колекцій.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ