JavaRush /Курси /C# SELF /Налаштування XML-серіалізації

Налаштування XML-серіалізації

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

1. Вступ

Ви вже знаєте, що .NET уміє серіалізувати й десеріалізувати об’єкти в XML за замовчуванням, спираючись на публічні поля та властивості. Та коли потрібно отримати акуратний XML, сумісний із зовнішніми програмами й стандартами, або коли не слід додавати до XML певні внутрішні деталі, знадобиться тонке налаштування.

У C# для цього призначені атрибути серіалізації — спеціальні «мітки», якими позначають класи, властивості або поля. Завдяки їм можна керувати практично всім: які поля потраплять до XML, які ігнорувати, як саме називатиметься елемент, чи перетворювати його на атрибут, чи надавати йому складну структуру, а також визначати порядок елементів.

Уявімо серіалізацію за замовчуванням як автозаповнення анкети: імʼя, прізвище, по батькові — у свої клітинки, решта — як вийде. А з атрибутами ви самі вирішуєте, який рядок куди записати, що пропустити, що виділити, а що взагалі сховати.

Відповідність об’єкта та XML

Клас/Властивість Атрибут Результат у XML
[XmlRoot("Persons")]
Клас UserList
<Persons>...</Persons>
List<User> Users
[XmlElement("Person")]
<Person>...</Person>
int Id
[XmlAttribute("id")]
id="..." (атрибут)
string Name
[XmlElement("FullName")]
<FullName>...</FullName>
string[] Emails
[XmlArray("EMails")][XmlArrayItem("Email")]
<EMails><Email>...</Email></EMails>
DateTime RegisteredAt
[XmlIgnore] (див. RegisteredAtString) -
string RegisteredAtString
[XmlAttribute("registered")]
registered="..."
Address[] Addresses
[XmlArray("UserAddresses")][XmlArrayItem("Address")]
<UserAddresses>...</UserAddresses>

2. Огляд основних атрибутів XML-серіалізації

Пройдімося найпоширенішими атрибутами, які .NET‑розробники використовують уже багато років!

Атрибут [XmlElement] — ім’я елемента

Змінює ім’я елемента в XML або позначає властивість/поле як XML‑елемент.

public class Person
{
    [XmlElement("FullName")]
    public string Name { get; set; }
}

XML:

<Person>
  <FullName>Василь Петров</FullName>
</Person>

Якщо атрибут не вказати — за замовчуванням ім’я елемента збігатиметься з ім’ям властивості/поля.

Атрибут [XmlAttribute] — серіалізація у XML-атрибут

Дозволяє серіалізувати властивість/поле як атрибут XML, а не як елемент.

public class Person
{
    [XmlAttribute("id")]
    public int Id { get; set; }
}

XML:

<Person id="123"></Person>

У реальних XML‑API атрибути часто використовують для ідентифікаторів, дат, прапорців тощо.

Атрибут [XmlIgnore] — пропустити поле/властивість

Цей атрибут повністю виключає властивість із процесу серіалізації та десеріалізації: позначене ним поле або властивість просто не потрапить до фінального XML.

public class Person
{
    [XmlIgnore]
    public string InternalNote { get; set; }
}

XML: (властивості InternalNote тут немає і не буде)

Атрибути [XmlArray] і [XmlArrayItem] — контроль над колекціями

Спеціальні атрибути для масивів і колекцій. [XmlArray] задає ім’я зовнішньої обгортки масиву, а [XmlArrayItem] — ім’я окремого елемента.

public class Person
{
    [XmlArray("Phones")]
    [XmlArrayItem("Phone")]
    public string[] PhoneNumbers { get; set; }
}

XML:

<Person>
  <Phones>
    <Phone>+12951234567</Phone>
    <Phone>+12876543210</Phone>
  </Phones>
</Person>

Атрибут [XmlRoot] — назва кореневого елемента

Позначає сам клас, щоб змінити ім’я кореневого елемента в XML.

[XmlRoot("User")]
public class Person
{
    public string Name { get; set; }
}

XML:

<User>
  <Name>Анна</Name>
</User>

Атрибут [XmlText] — серіалізація у текстовий вміст елемента

Іноді потрібно, щоб значення властивості стало просто текстом усередині елемента, а не його вкладеним піделементом або атрибутом.

public class Note
{
    [XmlText]
    public string Content { get; set; }
}

XML:

<Note>Зателефонувати бабусі!</Note>

Атрибути [XmlNamespaceDeclarations], [XmlElement(Type = ...)]

Для складніших сценаріїв (наприклад, робота з просторами імен, підтримка наслідування тощо) існують і специфічніші атрибути. Радимо переглянути офіційну документацію Microsoft для повного переліку.

3. Практика: удосконалюємо серіалізацію на реальному прикладі

Пройдімо крок за кроком і удосконалимо наш навчальний застосунок, довівши XML‑серіалізацію до високого рівня — щоб вона відповідала навіть найприскіпливішим вимогам.

Базовий клас без атрибутів

Припустімо, у нас є такий клас користувача:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string[] Emails { get; set; }
    public DateTime RegisteredAt { get; set; }
    public string Password { get; set; }
}

Серіалізація за замовчуванням сформує приблизно такий XML (і ще й дату збереже у власному форматі):

<User>
  <Id>42</Id>
  <Name>Ivan Ivanov</Name>
  <Emails>
    <string>user@mail.com</string>
    <string>admin@site.org</string>
  </Emails>
  <RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
  <Password>qwerty</Password>
</User>

Що не так:

  • Пароль краще взагалі не серіалізувати.
  • Хочемо мати дату як атрибут і в зрозумілому форматі.
  • Хочемо список електронних адрес у зручному вигляді.
  • Хочемо кореневий елемент <Person>.

Додаємо налаштування через атрибути

[XmlRoot("Person")]
public class User
{
    [XmlAttribute("id")]
    public int Id { get; set; }

    [XmlElement("FullName")]
    public string Name { get; set; }

    [XmlArray("EMails")]
    [XmlArrayItem("Email")]
    public string[] Emails { get; set; }

    [XmlAttribute("registered")]
    public string RegisteredAtString
    {
        get => RegisteredAt.ToString("yyyy-MM-dd");
        set => RegisteredAt = DateTime.Parse(value);
    }

    [XmlIgnore]
    public string Password { get; set; }

    [XmlIgnore]
    public DateTime RegisteredAt { get; set; }
}

Пояснення тонкощів:

  • Використовуємо [XmlAttribute("registered")] для серіалізації дати як атрибута.
  • Щоб серіалізувати не сам DateTime, а його рядкове представлення, додаємо окрему властивість RegisteredAtString, а реальне поле приховуємо через [XmlIgnore].
  • Пароль приховуємо за допомогою [XmlIgnore].
  • Масив електронних адрес налаштовано так, що зовнішній тег — <EMails>, а кожна адреса — <Email>.

Результат:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
    <Email>admin@site.org</Email>
  </EMails>
</Person>

4. Робота з вкладеними об’єктами

XML особливо зручний, коли є вкладені об’єкти (наприклад, користувач і його адреси).

public class Address
{
    [XmlAttribute]
    public string City { get; set; }

    [XmlText]
    public string Details { get; set; }
}

public class User
{
    // ...інші властивості, див. вище...

    public Address[] Addresses { get; set; }
}

Додамо налаштування для масиву адрес:

[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }

Тоді XML виглядатиме так:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
  </EMails>
  <UserAddresses>
    <Address City="Neon city">вул. Замкова, буд. 1</Address>
    <Address City="North Cave">вул. Крайня буд. 12</Address>
  </UserAddresses>
</Person>

Видно, що в кожному <Address> атрибут City містить назву міста, а саму адресу вказано як текст усередині елемента.

5. Поширені помилки та нюанси

Якщо ви серіалізуєте складний об’єкт і не бачите у XML очікуваних змін — найімовірніше, забули правильно вказати атрибут. Іноді буває, що властивості/поля залишаються без маркування. Тоді .NET просто обирає поведінку за замовчуванням.

Ще одна пастка — ім’я колекції. Якщо колекція (наприклад, масив рядків) не позначена ні [XmlArray], ні [XmlArrayItem], то елементи запишуться з тегом string (як видно в початковому прикладі вище). Щоб цього не було, завжди явно задавайте імена тегів через атрибути.

Будьте обережні з властивостями типу «getter‑only» (лише для читання) — такі властивості за замовчуванням не десеріалізуються. Намагайтеся залишати публічний set.

Деякі типи (наприклад, словники або інтерфейси) стандартний XmlSerializer не серіалізує або вони потребують окремої обробки.

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