«Хороший сайт делается для людей, а поисковики приходят следом»

— из принципов редакции SeoLiga.ru
Главная » Статьи » .NET и C# » Сериализация объектов: XML и JSON

Сериализация объектов: XML и JSON

Рано или поздно любое приложение должно сохранить состояние: записать настройки на диск, передать данные другой программе, отправить ответ клиенту веб-сервиса. Объекты в памяти для этого не годятся: они живут только внутри процесса. Нужен способ превратить их в последовательность символов или байтов и потом восстановить. Этот процесс называется сериализацией, а обратный — десериализацией.

В мире .NET два самых популярных текстовых формата — XML и JSON. Разберём, как с ними работать, в чём их различия и как выбрать подходящий вариант для своей задачи.

Модель данных для примеров

Возьмём простой класс заказа. Все примеры дальше будут использовать его.

public class Order
{
    public int Id { get; set; }
    public string Customer { get; set; }
    public decimal Total { get; set; }
    public List<string> Items { get; set; } = new List<string>();
}

Для сериализации нужен публичный класс с публичными свойствами. Для XML-сериализатора дополнительно требуется открытый конструктор без параметров.

XML-сериализация

В стандартной библиотеке за неё отвечает класс XmlSerializer. Он строит документ на основе структуры типа.

var order = new Order { Id = 7, Customer = "Иван", Total = 1250.50m };
order.Items.Add("Клавиатура");
order.Items.Add("Мышь");

var serializer = new XmlSerializer(typeof(Order));
using (var writer = new StringWriter())
{
    serializer.Serialize(writer, order);
    string xml = writer.ToString();
}

Результат будет выглядеть примерно так: корневой элемент Order, внутри него элементы Id, Customer, Total и вложенный список Items с дочерними элементами string. Для чтения используется метод Deserialize, который принимает поток или читатель и возвращает object, приводимый к нужному типу.

Управление форматом XML

Атрибуты позволяют точно настроить вид документа:

  • XmlRoot — задаёт имя корневого элемента;
  • XmlElement — меняет имя элемента свойства;
  • XmlAttribute — выводит значение в виде атрибута вместо вложенного элемента;
  • XmlIgnore — исключает свойство из результата;
  • XmlArray и XmlArrayItem — управляют именами коллекции и её элементов.
[XmlRoot("order")]
public class Order
{
    [XmlAttribute("id")]
    public int Id { get; set; }

    [XmlElement("customer")]
    public string Customer { get; set; }

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

JSON-сериализация

Современный способ — класс JsonSerializer из пространства имён System.Text.Json. Раньше де-факто стандартом была сторонняя библиотека Newtonsoft.Json, и она до сих пор встречается в проектах. Идея у обоих вариантов одна.

string json = JsonSerializer.Serialize(order);
Order restored = JsonSerializer.Deserialize<Order>(json);

Получится компактный текст с фигурными скобками, парами «имя: значение» и массивом для списка. JSON короче XML и проще читается человеком, потому что не содержит закрывающих тегов.

Настройка JSON

Поведение задаётся через объект JsonSerializerOptions и атрибуты:

  • JsonPropertyName — переименовывает свойство;
  • JsonIgnore — пропускает свойство;
  • PropertyNamingPolicy — задаёт общий стиль имён, например camelCase;
  • WriteIndented — включает форматирование с отступами, удобное для отладки;
  • DefaultIgnoreCondition — позволяет не записывать значения по умолчанию или null.
var options = new JsonSerializerOptions
{
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string json = JsonSerializer.Serialize(order, options);

Сравнение форматов

КритерийXMLJSON
РазмерБольше из-за теговКомпактнее
ЧитаемостьХорошая, но многословнаяОтличная для простых структур
Схема и валидацияЗрелые средства (схемы, трансформации)Есть, но менее распространены
Атрибуты и пространства имёнПоддерживаютсяНет аналога
Типичное применениеКонфигурации, документы, обмен с устаревшими системамиВеб-интерфейсы, мобильные клиенты, настройки

Если вы пишете веб-сервис для современного клиента, берите JSON. Если интегрируетесь со старой корпоративной системой, которая говорит на XML, или вам нужны схемы и трансформации, выбирайте XML.

Что не сериализуется или сериализуется неожиданно

Каждый, кто работал с сериализацией, натыкался на эти грабли:

  • Циклические ссылки. Если родитель ссылается на потомка, а потомок на родителя, сериализатор уйдёт в бесконечную рекурсию или бросит исключение. Решение: пометить обратную ссылку как игнорируемую или настроить обработку циклов.
  • Интерфейсы и абстрактные классы. Сериализатор не знает, какой именно потомок лежит в свойстве. Нужны атрибуты с перечислением типов или собственный конвертер.
  • Словари. XmlSerializer не умеет работать с Dictionary без обходных приёмов, а JSON справляется нормально.
  • Закрытые поля. По умолчанию в результат попадают только публичные свойства.
  • Даты и числа. Формат даты и разделитель дробной части могут зависеть от региональных настроек, поэтому задавайте формат явно.

Работа с файлами и потоками

Сохранять строку в память нужно не всегда. Для файлов лучше писать прямо в поток, чтобы не держать весь документ в памяти.

using (var stream = File.Create("order.json"))
{
    JsonSerializer.Serialize(stream, order, options);
}

using (var stream = File.OpenRead("order.json"))
{
    var order2 = JsonSerializer.Deserialize<Order>(stream);
}

Для больших данных у JSON есть асинхронные методы, которые читают и пишут поток без блокировок. Для XML при огромных документах стоит использовать потоковые XmlReader и XmlWriter, которые не загружают всё дерево сразу.

Версионирование данных

Файл настроек, записанный в прошлой версии программы, должен читаться и в новой. Несколько приёмов помогают пережить эволюцию формата:

  1. Добавляйте новые свойства со значениями по умолчанию. Старый файл их просто не содержит, и они получат дефолт.
  2. Не удаляйте и не переименовывайте свойства без необходимости. Если переименовали, оставьте старое имя через атрибут или конвертер.
  3. Храните в документе поле с номером версии формата и при чтении выполняйте миграцию.
  4. Не падайте на неизвестных полях. Лишние данные лучше игнорировать, чем отказываться читать файл.

Обработка ошибок

Данные из внешнего источника нельзя считать корректными. Файл мог быть повреждён, отредактирован вручную или прийти от злонамеренного клиента. Оборачивайте десериализацию в try и ловите исключения: JsonException для JSON и InvalidOperationException для XML. После чтения проверяйте инварианты объекта: допустимые диапазоны, обязательные поля, ссылочную целостность. Для данных от недоверенных источников не используйте механизмы, которые создают произвольные типы по имени из входного документа: это классический путь к уязвимостям.

Производительность и выбор подхода

Для небольших объектов разница в скорости не заметна, но при обработке больших потоков данных она начинает иметь значение. Практические советы:

  • создавайте XmlSerializer один раз и переиспользуйте: его конструктор дорогой, так как строит код для типа;
  • для JSON переиспользуйте объект настроек, а не создавайте новый на каждый вызов;
  • при чтении большого файла используйте потоки, а не загрузку целиком в строку;
  • не сериализуйте лишнее: отдельные модели для передачи данных короче и безопаснее, чем выгрузка всей доменной сущности.

Последний пункт особенно важен для веб-интерфейсов. Если отдать наружу внутренний класс как есть, вы можете случайно раскрыть служебные поля, например хеш пароля или признаки доступа.

Заключение

Сериализация — базовый навык, который пригождается в каждом проекте. XML даёт строгость, схемы и богатую структуру, JSON — краткость и удобство обмена с веб-средой. Освойте оба инструмента, научитесь управлять форматом через атрибуты и параметры, думайте о версиях данных и о защите от кривого ввода. Попробуйте сохранить собственный класс настроек в оба формата и сравнить результаты: на практике разница чувствуется лучше, чем в таблице.

АВ
Андрей Ветров
Автор SeoLiga.ru. Пишет о практике: что работает на реальных проектах, а что лучше обойти стороной. Хотите предложить свою статью? Загляните в раздел «Авторам».