Перейти к основному содержимому

5.05. Сериализация и парсинг

Разработчику Архитектору

Сериализация и парсинг

JSON: System.Text.Json, Newtonsoft.Json
XML: XmlSerializer, XDocument, LINQ to XML
Бинарная, SOAP-сериализация (кратко)

В HTTP-запросах к API, сохранении настроек, обмене данными между микросервисами, кэшировании (например, в Redis) используется сериализация и десериализация.

Сериализация - это преобразование объекта C# в формат, пригодный для хранения или передачи (например, в строку JSON), а десериализация - восстановление объекта из такого формата.

var user = new { Name = "Timur", Age = 30 };

// Сериализация: объект → JSON
string json = JsonSerializer.Serialize(user);
// Результат: {"Name":"Timur","Age":30}

// Десериализация: JSON → объект
var restored = JsonSerializer.Deserialize<UserDto>(json);

Для работы с JSON испольуется Newtonsoft.Json или System.Text.Json.

Начиная с .NET Core 3.0, Microsoft добавила в ядро мощную библиотеку — System.Text.Json, поэтому «в коробке» есть средство для работы, встроенное в .NET, с высокой производительностью. Но для сложных сценариев лучше использовать Newtonsoft.Json.

Большинство проектов до сих пор используют Newtonsoft.Json (также известный как Json.NET) — самую популярную стороннюю библиотеку для работы с JSON.

using Newtonsoft.Json;

string json = JsonConvert.SerializeObject(user);
User restored = JsonConvert.DeserializeObject<User>(json);

Парсинг JSON - важный навык. Можно записывать какие-то данные в этот формат обмена и передавать, затем читать на сервере, разбивая на сопутствующие элементы и обрабатывая результаты.

Иногда структура JSON неизвестна заранее — например, при интеграции с внешним API. Тогда помогает LINQ-to-JSON. Это динамический парсинг, использующий JObject и JArray (объект и массив соответственно).

string json = @"{
'name': Timur,
'hobbies': ['reading', 'coding'],
'address': { 'city': 'Moscow' }
}";

JObject obj = JObject.Parse(json);

string name = (string)obj["name"];
string city = (string)obj["address"]["city"];
JArray hobbies = (JArray)obj["hobbies"];

Это полезно, когда API возвращает разные поля в зависимости от контекста, или нужно извлечь только часть данных. Также можно писать таким образом универсальный клиент.

Хотя JSON стал стандартом, XML всё ещё используется — в SOAP, конфигурациях, старых API. Хотя частенько можно увидеть комбинацию XML+Java, но всё же и в C# используется из-за распространённости форматов. Для работы с ним используется сериализация XML:

using System.Xml.Serialization;
using System.IO;

var user = new User { Name = "Timur", Age = 25 };
var serializer = new XmlSerializer(typeof(User));

using var writer = new StringWriter();
serializer.Serialize(writer, user);
string xml = writer.ToString();

Как результат:

<User>
<Name>Timur</Name>
<Age>25</Age>
</User>

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

string json = JsonSerializer.Serialize(user);
byte[] bytes = Encoding.UTF8.GetBytes(json);

// Отправка в теле запроса
var content = new ByteArrayContent(bytes);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");

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

Чтобы работать с этими данными, приложения используют:

  • ORM для удобной работы с БД через объекты в коде;
  • Файловые операции - чтение/запись файлов напрямую.

Обмен данными - это интеграция, когда одно приложение (или сервис) хочет поделиться данными с другим. Она почти всегда происходит через HTTP-запросы, сообщения (в очередях) или файловые обмены. К примеру, веб-сервис на C# отправляет данные о заказе в систему учёта на Python.

Данные нельзя передать «как есть» - их нужно упаковать в понятный формат:

  • Простые объекты (пользователь, заказ) передается в JSON или XML;
  • Текст - как строка (plain text);
  • Файлы (PDF, изображения) - как поток байтов (бинарные данные).

Это и есть сериализация - преобразование объекта в формат, пригодный для передачи.

Отправка происходит так - объект сериализуется в JSON/байты, затем передаётся по сети.

Получение - данные десериализуются и становятся объектом в принимающем приложении.

Поэтому нужно, чтобы отправитель и получатель были готовы к такой интеграции, подготовив соответствующие системы хранения и обработки, и конечно продумав и реализовав модели, и вся структура должна быть согласована.

Это работает по-разному, к примеру, может быть некий сервис, который просто реализует возможность предоставлять данные из своих баз. Для этого он просто реализует методы со своей внутренней логикой, и публикуется по адресу - эндпоинту. Затем формируется документация, где описывается, какие методы можно вызывать, как обращаться, как должны выглядеть запросы, и как будут выглядеть ответы. В такой ситуации, запрашивающим системам (которые будут интегрироваться) нужно будет просто изучить документацию и обеспечить, чтобы запросы были такими же, как ожидается, а ответы могли обрабатываться и использоваться. И зачастую неважно, как системы это делают внутри - главное чтобы исходящие/входящие данные были корректными.

Бинарные данные - особый случай. PDF, фото, видео - это байты. Их нельзя просто впихнуть в JSON, поэтому существует два подхода:

  1. отдельный запрос на загрузку файла - сначала передаётся метаинформация (как раз JSON), а затем файл отправляется отдельно (например, через multipart/form-data).

  2. кодирование в строку (Base64), это конечно неэффективно по объёму, но удобно для встраивания, будет выглядеть так:

{
"filename": "report.pdf",
"data": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9MZW5ndGggND..."
}

И всё это в итоге является интеграционным потоком - конвейером, где происходит работа по цепочке:

[Объект в памяти] - [Сериализация] - [Передача] - [Десериализация] - [Обработка]