Содержание
Формат JSON, в настоящее время, один из наиболее часто используемых текстовых форматов данных, используемых как для хранения информации об объектах, так и для обмена этой информацией по Сети. В свою очередь, сериализация JSON — это очень важный момент при разработке различных веб-приложений. И сегодня мы рассмотрим один из вариантов сериализации/десериализации объектов C# с использованием классов и объектов, расположенных в пространстве имен System.Text.Json
. Этот сериализатор с момента выхода .NET Core 3 претерпел значительные изменения и показывает достаточно хорошие результаты тестов.
Что такое «сериализация»?
В соответствии с определением, данным Microsoft, сериализация — это процесс преобразования объекта в поток байтов для сохранения или передачи в память, базу данных или файл. Сериализация используется для того, чтобы сохранить состояния объекта для последующего воссоздания при необходимости. Обратный процесс называется десериализацией.
Например, при разработке приложений в Visual Studio, активно используются файлы в формате JSON, позволяющие сохранять настройки вашего приложения и восстанавливать эти настройки при запуске. Например, в C# вы можете сохранить (сериализовать) положение и размер главного окна приложения и при повторном запуске приложения восстановить (десериализовать) эти настройки объекта (главного окна приложения).
Для сериализации/десериализации JSON в C# можно использовать как штатный сериализатор JsonSerializer
, расположенный в пространстве имен System.Text.Json
, так и решения сторонних разработчиков, например, часто используемый JSON.NET. Мы будем использовать штатный сериализатор.
Пример сериализации и десериализации JSON
Допустим, у нас имеется вот такой класс для хранения информации о человеке:
public class Person { public string Name { get; set; } public string Surname { get; set; } private int age; public int Age { get => age; set { if ((value < 0) || (value > 100)) throw new Exception("Возраст должен находится в интервале от 0 до 100 лет"); else age = value; } } public DateTime Birthday{ get; set; } }
Создадим объект этого класса и сохраним состояние объекта в файл в формате JSON:
Person person = new Person() { Name = "Вася", Surname = "Пупкин", Age = 38, Birthday = new DateTime(1983, 01, 16) }; string personJson = JsonSerializer.Serialize(person, typeof(Person)); StreamWriter file = File.CreateText("person.json"); file.WriteLine(personJson); file.Close();
Здесь мы выполнили следующие действия:
- Создали сам объект для сериализации (
person
) - Сериализовали объект в строку (
string
) используя сериализаторJsonSerializer
- Записали полученную строку в текстовый файл
person.json
.
Теперь рядом с exe-файлом приложения появился файл со следующим содержимым:
{"Name":"\u0412\u0430\u0441\u044F","Surname":"\u041F\u0443\u043F\u043A\u0438\u043D","Age":38,"Birthday":"1983-01-16T00:00:00"}
Теперь мы можем при следующем запуске программы восстановить (десериализовать) этот объект в нашей программе, например, так:
if (File.Exists("person.json")) { string data = File.ReadAllText("person.json"); Person person = JsonSerializer.Deserialize<Person>(data); Console.WriteLine($"Имя: {person.Name}"); Console.WriteLine($"Фамилия: {person.Surname}"); Console.WriteLine($"Возраст: {person.Age}"); Console.WriteLine($"День рождения: {person.Birthday}"); }
Результат:
Фамилия: Пупкин
Возраст: 38
День рождения: 16.01.1983 0:00:00
Настройки сериализации JSON
Настройка сериализации с помощью JsonSerializerOptions
Для настройки сериализации мы можем передать в метод Serialize
второй параметр — объект класса JsonSerializerOptions
, который содержит настройки сериализации/десериализации объектов. Основные свойства этого класса следующие:
bool AllowTrailingCommas
— устанавливает, надо ли добавлять после последнего элемента в json запятую. Если равноtrue
, запятая добавляетсяJsonNamingPolicy DictionaryKeyPolicy
— возвращает или задает политику, используемую для преобразования имени ключаIDictionary
в другой формат, например CamelCase.JavaScriptEncoder Encoder
— Возвращает или устанавливает кодировщик, используемый при экранировании строк. Укажите значениеnull
для использования кодировщика по умолчаниюbool IgnoreNullValues
— устанавливает, будут ли сериализоваться/десериализоваться в json объекты и их свойства со значениемnull
bool IgnoreReadOnlyProperties
— аналогично устанавливает, будут ли сериализоваться свойства, предназначенные только для чтенияint MaxDepth
— возвращает или задает максимальную глубину, разрешенную при сериализации или десериализации JSON, при этом значение по умолчанию0
указывает максимальную глубину64
.bool PropertyNameCaseInsensitive
— возвращает или задает значение, которое определяет, использует ли имя свойства сравнение без учета регистра во время десериализации. Значение по умолчанию —false
.JsonNamingPolicy PropertyNamingPolicy
— возвращает или задает значение, указывающее политику, используемую для преобразования имени свойства объекта в другой формат, например CamelCase стиль, илиnull
, чтобы оставить имена свойств без изменений.JsonCommentHandling ReadCommentHandling
— Возвращает или задает значение, определяющее, как комментарии обрабатываются во время десериализацииbool WriteIndented
— устанавливает, будут ли добавляться в json пробелы (условно говоря, для красоты). Если равноtrue
устанавливаются дополнительные пробелы
Рассмотрим несколько примеров использования этих настроек сериализации Json.
Как получить форматированную строку (с лидирующими пробелами) Json?
Для этого необходимо в настройках сериализации указать значение WriteIndented = true
:
Person person = new Person() { Name = "Вася", Surname = "Пупкин", Age = 38, Birthday = new DateTime(1983, 01, 16) }; JsonSerializerOptions options = new JsonSerializerOptions() { WriteIndented = true }; string personJson = JsonSerializer.Serialize(person, options); Console.WriteLine(personJson);
Результат
{
"Name": "\u0412\u0430\u0441\u044F",
"Surname": "\u041F\u0443\u043F\u043A\u0438\u043D",
"Age": 38,
"Birthday": "1983-01-16T00:00:00"
}
Как запретить экранировать строки при сериализации JSON?
В примере выше у нас все строки (имя, фамилия) экранированы. С точки зрения безопасности — это полностью правильное решение, но не всегда удобно для чтения. Чтобы позволить сериализатору не экранировать символы в строках мы можем воспользоваться свойством Encoder
у JsonSerializerOptions
следующим образом:
using System.Text.Encodings.Web; JsonSerializerOptions options = new JsonSerializerOptions() { WriteIndented = true, //добавляем пробелы для красоты Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping //не экранируем символы в строках };
в этом случае, объект из примера выше будет выглядеть следующим образом:
{
"Name": "Вася",
"Surname": "Пупкин",
"Age": 38,
"Birthday": "1983-01-16T00:00:00"
}
Как сериализовать имена свойств в CamelCase?
По умолчанию, сериализация имен свойств объекта проводится как есть, однако, если вам необходимо применить к именам свойств стиль CamelCase (он же «верблюжий»), то можно использовать следующую настройку сериализации:
using System.Text.Encodings.Web; JsonSerializerOptions options = new JsonSerializerOptions() { .... PropertyNamingPolicy = JsonNamingPolicy.CamelCase //используем стиль CamekCase };
Теперь наш объект в JSON будет выглядеть следующим образом:
{
"name": "Вася",
"surname": "Пупкин",
"age": 38,
"birthday": "1983-01-16T00:00:00"
}
Настройка сериализации с помощью атрибутов
При сериализации/десериализации объектов в JSON бывает необходимым исключить какое-либо свойство из строки JSON. Например, в нашем объекте Person
таким свойством может быть свойство Age
, так как мы можем легко рассчитать возраст человека, зная его день рождения и каждый раз «таскать» это свойство в JSON — смысла нет. В этом случае нам может пригодиться настройка сериализации JSON с использованием атрибутов. Для того, чтобы воспользоваться таким видом настройки, необходимо подключить пространство имен System.Text.Json.Serialization
.
Исключим свойство Age
из сериализуемых, используя атрибут JsonIgnore
:
public class Person { public string Name { get; set; } public string Surname { get; set; } private int age; [JsonIgnore] public int Age //теперь свойство Age не будет сериализоваться { get => age; set { if ((value < 0) || (value > 100)) throw new Exception("Возраст должен находится в интервале от 0 до 100 лет"); else age = value; } } public DateTime Birthday { get; set; } }
Также, часто используемым атрибутом для настройки сериализации/десериализации JSON является атрибут JsonPropertyName
, который позволяет задать произвольное имя сериализуемому свойству. Например,
public class Person { [JsonPropertyName("PersonFamily")] public string Surname { get; set; } }
Теперь свойство Surname
будет сериализоваться в JSON с именем PersonFamily
.
Особенности сериализации и десериализации JSON
При сериализации/десериализации JSON с использованием штатного сериализатора JsonSerializer
необходимо обратить внимание на следующие моменты:
Десериализуемый объект должен иметь конструктор без параметров
Например, во всех примерах по работе с JSON в C# мы использовали конструктор по умолчанию. Если бы наш класс имел, например, только вот такой конструктор:
public class Person { public string Name { get; set; } .... public Person(string name) { Name = name; } }
То при попытке десериализовать такой объект мы получим исключение:
Сериализации подлежат только публичные свойства
Все непубличные свойства, а также поля сериализуемого класса игнорируются сериализатором.
Итого
Сегодня мы рассмотрели основные моменты того, как в C# происходит сериализация и десериализация объектов в JSON. Научились использовать настройки сериализации и десериализации с использованием атрибутов и специального класса JsonSerializerOptions
. Конечно, возможности работы с JSON в C# этим не ограничиваются, но полученных знаний нам будет пока достаточно, чтобы попробовать использовать JSON в своих приложениях.