1. Вступ
Довгий час в екосистемі .NET головним інструментом для роботи з JSON був популярний сторонній пакет Newtonsoft.Json (він же Json.NET). Він потужний, гнучкий і досі широко використовується. Та з появою нових версій .NET 9 і C# 14 Microsoft вирішила, що настав час мати власний, вбудований, високопродуктивний JSON-серіалізатор. Так і з’явився System.Text.Json.
Навіщо потрібен новий підхід? System.Text.Json розроблено з урахуванням сучасних реалій і він закриває проблеми, що накопичилися за роки використання сторонніх бібліотек. Його оптимізовано на максимальну швидкість і безпеку, він ідеально підходить для асинхронних сценаріїв і веб-API — і, що найприємніше, не потребує встановлення через NuGet: усе вже в платформі.
Звісно, Newtonsoft.Json нікуди не подівся, і ми розглянемо його пізніше. Але для більшості нових проєктів System.Text.Json — вибір за замовчуванням. Приготуйтеся: зараз навчимо ваші об’єкти говорити мовою JSON!
2. Основи роботи з System.Text.Json
Проста серіалізація об’єкта
Почнімо з базового прикладу. Серіалізуємо наш об’єкт у JSON-рядок.
using System;
using System.Text.Json; // Не забудьте додати!
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
public bool IsAlive { get; set; }
}
// Десь у вашій програмі:
Player player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };
// Серіалізація:
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // Виведе: {"Name":"Aragorn","Health":100,"IsAlive":true}
Коментар: Якщо ви тільки починаєте вивчати серіалізацію, цей приклад показує, наскільки це просто: JsonSerializer.Serialize — і готово.
Десеріалізація об’єкта
Оживімо об’єкт із JSON-рядка:
string incomingJson = "{\"Name\":\"Legolas\",\"Health\":88,\"IsAlive\":true}";
Player player2 = JsonSerializer.Deserialize<Player>(incomingJson);
Console.WriteLine(player2.Name); // Legolas
Console.WriteLine(player2.Health); // 88
Console.WriteLine(player2.IsAlive); // true
Коментар: Якщо структура JSON збігається з вашим класом — усе працює як годинник. Якщо ні — можливі винятки або значення за замовчуванням.
3. Принципи роботи та влаштування серіалізації
Як відбувається зіставлення
System.Text.Json за замовчуванням використовує ті самі імена властивостей, що й у класі. Регістр має значення! Якщо в JSON написано health замість Health, десеріалізація не спрацює — властивість залишиться зі значенням за замовчуванням (0, false або null).
Наприклад:
// JSON із ключами в нижньому регістрі:
string badJson = "{\"name\":\"Gimli\",\"health\":120,\"isAlive\":true}";
Player player3 = JsonSerializer.Deserialize<Player>(badJson);
Console.WriteLine(player3.Name); // порожньо
Console.WriteLine(player3.Health); // 0
Console.WriteLine(player3.IsAlive); // false
Цікавий факт: Багато API пишуть ключі в camelCase (health), а в C# прийнято PascalCase (Health). Це вирішується налаштуваннями — нижче.
4. Керування серіалізацією — опції та налаштування
Форматування JSON: «зручний для читання» вивід
Іноді потрібен не компактний, а красиво відформатований JSON — для файлів конфігурації або журналів.
var options = new JsonSerializerOptions
{
WriteIndented = true // Додати відступи
};
string prettyJson = JsonSerializer.Serialize(player, options);
Console.WriteLine(prettyJson);
/*
{
"Name": "Frodo",
"Health": 50,
"IsAlive": true,
"Inventory": [
"Ring",
"Bread",
"Torch"
],
"Position": {
"X": 5,
"Y": 15
}
}
*/
Активуйте властивість WriteIndented, і серіалізатор додасть відступи та перенесення рядків.
Керування стилем імен (CamelCase vs PascalCase)
Якщо ви працюєте із веб-API, де всі ключі в camelCase, увімкніть політику імен:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string camelCaseJson = JsonSerializer.Serialize(player, options);
// {"name":"Frodo","health":50,"isAlive":true,"inventory":["Ring","Bread","Torch"],"position":{"x":5,"y":15}}
Тоді й під час десеріалізації такі ключі коректно зіставлятимуться:
string apiJson = "{\"name\":\"Bilbo\",\"health\":40,\"isAlive\":true,\"inventory\":[\"Mug\"],\"position\":{\"x\":10,\"y\":5}}";
Player bilbo = JsonSerializer.Deserialize<Player>(apiJson, options);
Console.WriteLine(bilbo.Name); // Bilbo
5. Корисні нюанси
Використання атрибута [JsonIgnore]
Іноді не всі властивості потрібно серіалізувати — наприклад, приватні дані або тимчасові обчислювані значення.
using System.Text.Json.Serialization;
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
[JsonIgnore] // Ця властивість не потрапить у JSON
public bool IsSecretCharacter { get; set; }
}
Тепер під час серіалізації:
var player = new Player { Name = "Boromir", Health = 80, IsSecretCharacter = true };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Boromir","Health":80}
Під час зворотної десеріалізації IsSecretCharacter отримає значення за замовчуванням (false).
Використання [JsonPropertyName("...")]
Припустімо, у коді властивість називається IsAlive, а в JSON має бути "status":
using System.Text.Json.Serialization;
public class Player
{
public string Name { get; set; }
public int Health { get; set; }
[JsonPropertyName("status")]
public bool IsAlive { get; set; }
}
Серіалізація тепер виглядатиме так:
var player = new Player { Name = "Pippin", Health = 60, IsAlive = false };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Pippin","Health":60,"status":false}
І під час десеріалізації ключ "status" коректно заповнить властивість IsAlive.
Вбудовані обмеження та особливості безпеки
- За замовчуванням серіалізуються лише публічні властивості з гетерами/сетерами; приватні поля/властивості ігноруються.
- За наявності циклічних посилань генерується виняток: "A possible object cycle was detected".
- На відміну від Newtonsoft.Json, стандартний серіалізатор менше покладається на „магічні“ трюки з типами — зате безпечніший і швидший для типових сценаріїв.
6. Поширені помилки та підводні камені
Ви випадково змінюєте ім’я властивості в JSON і забуваєте оновити код — у результаті властивість отримує значення за замовчуванням (null, 0, false).
У JSON відсутнє потрібне поле — відповідна властивість об’єкта буде за замовчуванням (див. документацію).
У властивості немає публічного сетера — під час десеріалізації вона не заповниться.
Змінили структуру вкладених класів або колекцій — десеріалізація може зламатися або дати неочікувані результати.
Однакові імена на різних рівнях вкладеності (у батьківському й дочірньому об’єктах) збивають з пантелику та заважають налагодженню.
Іноді причина — у версії платформи: старі версії System.Text.Json гірше працювали з деякими типами (наприклад, Dictionary, DateTime, enum), але в .NET 7/8/9 багато що виправлено.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ