1. Введение
Всё отлично, если ваши потребности совпадают с замыслом по умолчанию. Но довольно часто заказчик (или бэкенд от третьих лиц) требует строго свой формат: везде camelCase, даты — ISO 8601, null-пропуски, кастомные конвертеры, и прочие радости интегратора.
Вот где появляется на сцене наш новый друг — класс JsonSerializerOptions.
Если бы сериализация была кухней, то этот класс — это ваши специи, кастрюли и секретные ингредиенты, которыми вы можете приправить сериализацию на свой вкус.
Как использовать JsonSerializerOptions
Чтобы кастомизировать процесс сериализации или десериализации, мы просто передаём объект настроек методам сериализатора:
using System.Text.Json;
var options = new JsonSerializerOptions
{
// Тут будут ваши настройки
};
string json = JsonSerializer.Serialize(myObject, options);
var obj = JsonSerializer.Deserialize<MyType>(json, options);
Такой паттерн встречается повсеместно: вы создаёте JsonSerializerOptions, настраиваете их по вкусу (см. далее), и передаёте в методы сериализатора Serialize/Deserialize.
Важно: Один и тот же объект JsonSerializerOptions вполне можно (и даже желательно) переиспользовать для множества сериализаций.
2. Основные настройки JsonSerializerOptions
Давайте рассмотрим самые популярные "ручки" и "переключатели" этого класса.
Форматирование JSON — WriteIndented
Мозолят ли вам глаза однострочные JSON-строки? Не беда — включите красивое форматирование с отступами!
var options = new JsonSerializerOptions
{
WriteIndented = true // Красивый, "человеческий" JSON
};
var person = new Person { Name = "Anna", Age = 30 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);
Вывод:
{
"Name": "Anna",
"Age": 30
}
Без WriteIndented = true вы бы получили скучный {"Name":"Anna","Age":30}. В продакшне чаще используют "минифицированный" JSON (он меньше весит), а для дебага и логов — форматированный.
Кейс-нейминг — PropertyNamingPolicy
Часто API требует, чтобы имена свойств были в стиле camelCase (например, firstName вместо FirstName). В .NET свойства обычно называют в PascalCase, и тут пригодится свойство PropertyNamingPolicy:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var person = new Person { Name = "Oleg", Age = 25 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"name":"Oleg","age":25}
Если вдруг нужен свой стиль (например, всё ЗАГЛАВНЫМИ буквами), можно написать собственную реализацию JsonNamingPolicy. Обычно хватает CamelCase.
3. Игнорирование свойств
Пропускать или не пропускать null-значения — DefaultIgnoreCondition
Иногда нужно, чтобы поля с пустыми (null) значениями вообще не появлялись в JSON. Причина — более компактный JSON, либо внешняя система не дружит с null.
Для этого используем свойство DefaultIgnoreCondition:
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var person = new Person { Name = null, Age = 22 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"Age":22}
Если хотите игнорировать не только null, но и дефолтные значения (например, int=0), используйте WhenWritingDefault:
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault;
Игнорировать приватные свойства — IncludeFields и [JsonInclude]
По умолчанию сериализуются только публичные свойства с открытым сеттером. Если хочется сериализовать ещё и поля (fields), можно включить:
var options = new JsonSerializerOptions
{
IncludeFields = true
};
Пример класса:
public class Item
{
public string Name;
public int Id { get; set; }
}
var item = new Item { Name = "Book", Id = 12 };
string json = JsonSerializer.Serialize(item, options);
// {"Name":"Book","Id":12}
Для сериализации приватных полей/свойств можно использовать атрибут [JsonInclude], но это — тема отдельной лекции.
4. Конвертация значений
Форматирование дат — Converters
По умолчанию System.Text.Json сериализует даты строго в формате ISO 8601 (например, "2023-12-27T15:30:45.123Z"), что почти всегда удобно. Но бывают ситуации, когда формат даты/времени должен быть особенным.
Для этого подключают свои конвертеры. Пример базовый:
using System.Text.Json;
using System.Text.Json.Serialization;
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
private string _format = "yyyyMMdd";
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var date = reader.GetString();
return DateTime.ParseExact(date, _format, null);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}
// Используем конвертер
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomDateTimeConverter());
var dateObj = new { Date = new DateTime(2022, 1, 5) };
string json = JsonSerializer.Serialize(dateObj, options); // {"Date":"20220105"}
5. Полезные нюансы
Не учитывать регистр свойств при десериализации — PropertyNameCaseInsensitive
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var json = "{\"name\":\"Vasya\",\"age\":33}";
var person = JsonSerializer.Deserialize<Person>(json, options);
// person.Name == "Vasya"
Управление вложенностью и максимальным уровнем объектов — MaxDepth
var options = new JsonSerializerOptions
{
MaxDepth = 32 // По умолчанию 64
};
Но если ваши объекты могут быть нескончаемы по вложенности — например, дерево с ссылками на родителей и детей — лучше проектировать модель иначе.
Комментарии в JSON — ReadCommentHandling (десериализация)
JSON официально не поддерживает комментарии, но иногда встречаются "творческие" файлы с // комментариями.
var options = new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Skip
};
string json = "{\n \"Name\": \"Ivan\", // Имя пользователя\n \"Age\": 30\n}";
var person = JsonSerializer.Deserialize<Person>(json, options);
Поддержка специальных символов — Encoder
Иногда нужно контролировать, как сериализатор экранирует символы (например, чтобы не переводить кириллицу в \uXXXX). Можно указать свой энкодер:
using System.Text.Encodings.Web;
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
Используйте с осторожностью, иначе ваши эмодзи/иероглифы могут внезапно "уехать" в Unicode-последовательности!
6. Типичные ошибки при настройке сериализации
Ошибка №1: забывают публичный сеттер. Поле или свойство не сериализуется, потому что у него нет публичного сеттера, или оно приватное.
Ошибка №2: неправильное игнорирование null-значений. При включённом DefaultIgnoreCondition свойства с null не попадут в JSON, хотя вы этого не ожидали.
Ошибка №3: неверный формат даты. Дата сериализуется не так, как нужно — подключите кастомный конвертер (JsonConverter).
Ошибка №4: ожидание фиксированного порядка свойств. Стандарт JSON не гарантирует порядок полей. Если API требует строгий порядок, придётся либо менять модель, либо использовать сторонние библиотеки.
Ошибка №5: разные настройки при сериализации и десериализации. Если вы не используете одинаковые JsonSerializerOptions для записи и чтения JSON, могут возникать ошибки, например, с регистром имён или форматами.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ