Работа с JSON в C#: сериализация и десериализация объектов

Формат 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();

Здесь мы выполнили следующие действия:

  1. Создали сам объект для сериализации (person)
  2. Сериализовали объект в строку (string) используя сериализатор JsonSerializer
  3. Записали полученную строку в текстовый файл 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;
    }
}

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

System.NotSupportedException: «Deserialization of reference types without parameterless constructor is not supported. Type ‘CSharpJson.Person'»

Сериализации подлежат только публичные свойства

Все непубличные свойства, а также поля сериализуемого класса игнорируются сериализатором.

Итого

Сегодня мы рассмотрели основные моменты того, как в C# происходит сериализация и десериализация объектов в JSON. Научились использовать настройки сериализации и десериализации с использованием атрибутов и специального класса JsonSerializerOptions. Конечно, возможности работы с JSON в C# этим не ограничиваются, но полученных знаний нам будет пока достаточно, чтобы попробовать использовать JSON в своих приложениях.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии