1. Для чего нужны атрибуты при сериализации?
Когда вы сериализуете объекты в JSON или XML, происходит что-то похожее на сканирование вашей комнаты роботом-уборщиком. Всё, что явно лежит на виду (публичные свойства/поля), попадает «в мешок» (в файл или строку), а остальное игнорируется. Но иногда вам не хочется, чтобы робот незаметно показывал содержимое вашего портфеля, или хочется аккуратно назвать вещи по-другому — например, не по-русски, а по-английски.
Тут и появляются атрибуты! Они позволяют управлять процессом сериализации: скрывать лишнее, менять имена, навешивать порядок, игнорировать отдельные части объекта или говорить сериализатору, что у вас есть особые правила. Это как стикеры на вещах: «не трогать», «особо важно», «переименовать в JSON».
Типовые задачи контроля сериализации
- Изменить имя свойства или поля в выходном документе (например, в JSON вместо FirstName сделать "first_name")
- Скрыть некоторые поля/свойства из сериализации или десериализации (например, пароли, внутренние счетчики)
- Управлять обработкой значений по умолчанию или null-значений
- Задать порядок элементов (актуально для XML)
- Описать дополнительные параметры (атрибуты) для XML-элементов
Каждая платформа сериализации — будь то System.Text.Json, Newtonsoft.Json или XmlSerializer — использует свои атрибуты для этих целей.
2. Атрибуты для System.Text.Json: современный и модный
Классический сериализатор JSON из системы .NET поддерживает неплохой список атрибутов, которые живут в пространстве имён System.Text.Json.Serialization.
Самые полезные атрибуты:
| Атрибут | Для чего нужен | Пример использования |
|---|---|---|
|
Преобразует имя свойства в JSON | |
|
Полностью исключает свойство из сериализации | |
|
Сериализует публичное поле (а не только свойство) | |
|
Игнорировать свойство при определённых условиях | |
Примеры использования:
using System.Text.Json.Serialization;
public class Person
{
[JsonPropertyName("first_name")]
public string FirstName { get; set; } // Имя в JSON будет 'first_name'
[JsonIgnore]
public string Password { get; set; } // Не попадет в JSON
[JsonPropertyName("born_year")]
public int? YearOfBirth { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Nickname { get; set; } // Если null — не попадет в JSON
}
Давайте сериализуем такого человека:
var person = new Person
{
FirstName = "Ivan",
Password = "123456",
YearOfBirth = 2000,
Nickname = null
};
string json = JsonSerializer.Serialize(person);
// json: {"first_name":"Ivan","born_year":2000}
Заметьте: и пароль, и прозвище пропали из JSON-базы! (Пароль — потому что игнорируется всегда, а прозвище — только если оно null).
Почему это важно? На практике вы часто не хотите, чтобы клиентская сторона (или злоумышленник!) увидел критичные данные вроде паролей, токенов, счётчиков, внутренних временных меток. С помощью [JsonIgnore] и подобных атрибутов это можно сделать в одну строку.
3. Атрибуты для Newtonsoft.Json: флагман гибкости
Если вы работаете с Newtonsoft.Json (документация здесь), то возможностей у вас станет ещё больше (и немного проще, если вы привыкли к старым проектам).
Основные атрибуты:
| Атрибут | Назначение |
|---|---|
|
Задает имя свойства в JSON, а также порядок, обязательность и др. |
|
Исключает поле/свойство из сериализации |
|
Требует обязательного наличия при десериализации |
|
Позволяет указать собственный конвертер для сложных случаев |
|
Управляет сериализацией значений по умолчанию |
Пример на практике:
using Newtonsoft.Json;
public class UserProfile
{
[JsonProperty("login")]
public string Username { get; set; }
[JsonIgnore]
public string InternalNotes { get; set; }
[JsonProperty(Required = Required.Always)]
public string Email { get; set; }
}
Дополнительные возможности JsonProperty — обязательность (Required), порядок (Order) и т.д. Например:
[JsonProperty("id", Order = 1, Required = Required.Always)]
public int Id { get; set; }
4. Атрибуты для XML: стиль "classic"
XmlSerializer использует целый арсенал собственных атрибутов из пространства имён System.Xml.Serialization.
Чаще всего встречающиеся:
| Атрибут | Для чего нужен |
|---|---|
|
Элемент в XML, с другим именем |
|
Преобразует свойство в атрибут XML-элемента |
|
Исключает свойство или поле из XML-сериализации |
|
Для коллекций — задаёт имя XML-массива |
|
Для коллекций — задаёт имя элемента массива |
|
Изменяет имя корневого XML-тега |
Пример на практике:
using System.Xml.Serialization;
[XmlRoot("human")]
public class Person
{
[XmlElement("firstname")]
public string Name { get; set; }
[XmlAttribute("years")]
public int Age { get; set; }
[XmlIgnore]
public string Secret { get; set; }
}
var person = new Person { Name = "Anna", Age = 32, Secret = "42" };
После сериализации XML будет примерно таким:
<human years="32"><firstname>Anna</firstname></human>
Обратите внимание, что поле Secret не попало в XML, а Age сериализовано как атрибут, а не как вложенный тег.
5. Полезные нюансы
Под капотом: как атрибуты работают
Когда сериализатор встречает ваш класс, он буквально «читается» рефлексией (магия .NET, подробнее см. System.Reflection). Сериализатор вчитывает метаданные: например, есть ли у свойства атрибут JsonIgnore или XmlElement. В зависимости от этого он добавляет данные в итоговый документ (или наоборот, пропускает).
Это удобный способ отделить «схему данных» от бизнес-логики. Ваш класс — это ваша бизнес-логика, а атрибуты — это, по сути, паспорт для сериализации.
Таблица соответствий ключевых атрибутов
| Функция | System.Text.Json | Newtonsoft.Json | XmlSerializer |
|---|---|---|---|
| Изменить имя | |
|
|
| Игнорировать | |
|
|
| Кастомный формат | |
|
— (через IXmlSerializable, больно) |
| Корень объекта | — | — | |
| Коллекция | — | — | |
6. Продвинутые сценарии
Иногда вам нужно сериализовать объект не так, как умеет стандартный сериализатор. Например, вы хотите хранить дату в формате UNIX timestamp, а не в стандартном ISO формате. Для такого случая есть атрибуты, которые позволяют подключить собственные конвертеры.
В System.Text.Json:
public class UnixDateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()).UtcDateTime;
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
=> writer.WriteNumberValue(new DateTimeOffset(value).ToUnixTimeSeconds());
}
public class LogEntry
{
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime EventTime { get; set; }
}
Теперь при сериализации поле EventTime превратится в число, а не строку с датой.
В Newtonsoft.Json:
public class BoolToYesNoConverter : JsonConverter<bool>
{
public override void WriteJson(JsonWriter writer, bool value, JsonSerializer serializer)
=> writer.WriteValue(value ? "yes" : "no");
public override bool ReadJson(JsonReader reader, Type objectType, bool existingValue, bool hasExistingValue, JsonSerializer serializer)
=> (string)reader.Value == "yes";
}
public class Answer
{
[JsonConverter(typeof(BoolToYesNoConverter))]
public bool IsCorrect { get; set; }
}
Теперь булево значение сериализуется в "yes" или "no", а обратно превращается в true или false.
7. Особенности и подводные камни
Когда вы добавляете атрибуты, помните: разные сериализаторы используют разные атрибуты. Если вы работаете и с JSON, и с XML (или, в крайнем случае, одновременно с Newtonsoft.Json и System.Text.Json), не забывайте ставить оба нужных атрибута, иначе будет сюрприз.
Стандартное имя свойства будет взято сериализатором, если только вы не переопределите его через атрибут.
Будьте внимательны при наследовании: дочерние классы наследуют публичные поля/свойства и, если на родителе был атрибут, он будет применяться и в дочернем классе. Это часто вызывает удивления, но так задумано.
Типичная ошибка: Очень часто встречается ситуация, когда разработчики случайно помечают поле, которое обязательно должно попасть в сериализацию, атрибутом JsonIgnore (или наоборот — забывают игнорировать чувствительные данные). Или, например, ставят XmlElement к приватному полю — и удивляются, почему сериализатор его не видит (XmlSerializer обрабатывает только public члены!).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ