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 = ...)]

Для более продвинутых сценариев (например, сериализация с пространствами имён, поддержка наследования и др.), есть и более специфические атрибуты. Обязательно посмотрите официальную документацию по XML-сериализации для полного списка.

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

Давайте шаг за шагом, доработаем наше учебное приложение до «XML-сериализации премиум-класса», чтобы оно радовало даже самого строгого 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>

Что не так:

  • Пароль лучше не сериализовать вообще.
  • Хотим дату как атрибут и человеческий формат.
  • Хотим Emails в красивой форме.
  • Хотим корневой элемент <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].
  • Массив email-ов настроен так, что внешний тег <EMails>, а каждый email — <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-ом, либо требуют отдельной обработки.

2
Задача
C# SELF, 48 уровень, 1 лекция
Недоступна
Сериализация коллекции с использованием XmlArray и XmlArrayItem
Сериализация коллекции с использованием XmlArray и XmlArrayItem
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Кирилл Уровень 51
3 января 2026
В табличке "Сопоставление объекта и XML" нужно поменять местами данные первой строчки примера вместо [XmlRoot("Persons")] Класс UserList сделать Класс UserList [XmlRoot("Persons")]