JavaRush /Курси /C# SELF /Серіалізація з System.Text...

Серіалізація з System.Text.Json

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

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 багато що виправлено.

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