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: класичний стиль
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 члени!).
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ