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 многое исправлено.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ