Просмотр содержимого документа
«Работа с XML и JSON»
Работа с XML и JSON
Определение JSON
- JSON (англ. JavaScript Object Notation) — текстовый формат обмена данными, основанный на JavaScript.
- JSON - простой, основанный на использовании текста, способ хранить и передавать структурированные данные. С помощью простого синтаксиса вы можете легко хранить все, что угодно, начиная от одного числа до строк, массивов и объектов, в простом тексте. Также можно связывать между собой массивы и объекты, создавая сложные структуры данных.
- После создания строки JSON, ее легко отправить другому приложению или в другое место сети, так как она представляет собой простой текст.
JSON имеет следующие преимущества:
- Он компактен.
- Его предложения легко читаются и составляются как человеком, так и компьютером.
- Его легко преобразовать в структуру данных для большинства языков программирования (числа, строки, логические переменные, массивы и так далее)
- Многие языки программирования имеют функции и библиотеки для чтения и создания структур JSON.
- Несмотря на происхождение от JavaScript (точнее, от подмножества языка стандарта ECMA-262 1999 года), формат считается независимым от языка и может использоваться практически с любым языком программирования. Для многих языков существует готовый код для создания и обработки данных в формате JSON.
Как создать строку JSON?
Есть несколько основных правил для создания строки JSON:
- Строка JSON содержит либо массив значений, либо объект (ассоциативный массив пар имя/значение).
- Массив заключается в квадратные скобки ([ и ]) и содержит разделенный запятой список значений.
- Объект заключается в фигурные скобки ({ и }) и содержит разделенный запятой список пар имя/значение.
- Пара имя/значение состоит из имени поля, заключенного в двойные кавычки, за которым следует двоеточие (:) и значение поля.
Значение в массиве или объекте может быть:
- Числом (целым или с плавающей точкой)
- Строкой (в двойных кавычках)
- Логическим значением (true или false)
- Другим массивом (заключенным в квадратные скобки)
- Другой объект (заключенный в фигурные скобки)
- Значение null
- Чтобы включить двойные кавычки в строку, нужно использовать обратную косую черту: \". Так же, как и во многих языках программирования, можно помещать управляющие символы и шестнадцатеричные коды в строку, предваряя их обратной косой чертой. Смотрите детали на сайте JSON.
Простой пример строки JSON
- Ниже приводится пример оформления заказа в формате JSON:
Рассмотрим строку подробно:
- Мы создаем объект с помощью фигурных скобок ({ и }).
- В объекте есть несколько пар имя/значение:
- В массиве "contents" есть 2 объекта, представляющие отдельные позиции в заказе. Каждый объект содержит 3 свойства: productID, productName, и quantity.
- Сериализация — это преобразование объекта или дерева объектов в какой-либо формат с тем, чтобы потом эти объекты можно было восстановить из этого формата. Используется, например, для сохранения состояния программы (то есть, некоторых её объектов) между запусками. Или для передачи данных между различными экземплярами программы (или различными программами), например, по сети.
- Главная идея состоит в том, что сериализованный формат — набор байт или строка, которую можно легко сохранить на диск или передать другому процессу или, например, по сети, в отличие от самого объекта. А значит, задача сохранения объекта/группы объектов при этом сводится к простой задаче сохранения набора байт или строки.
- JSON — один из популярных форматов для сериализации, он текстовый, легковесный и легко читается человеком.
Работа с JSON
- Сериализация в JSON. JsonSerializer
- Основная функциональность по работе с JSON сосредоточена в пространстве имен System.Text.Json.
- Ключевым типом является класс JsonSerializer, который и позволяет сериализовать объект в json и, наоборот, десериализовать код json в объект C#.
Для сохранения объекта в json в классе JsonSerializer определен статический метод Serialize(), который имеет ряд перегруженных версий. Некоторые из них:
- string Serialize(Object obj, Type type, JsonSerializerOptions options): сериализует объект obj типа type и возвращает код json в виде строки. Последний необязательный параметр options позволяет задать дополнительные опции сериализации
- string Serialize(T obj, JsonSerializerOptions options): типизированная версия сериализует объект obj типа T и возвращает код json в виде строки.
- Task SerializeAsync(Object obj, Type type, JsonSerializerOptions options): сериализует объект obj типа type и возвращает код json в виде строки. Последний необязательный параметр options позволяет задать дополнительные опции сериализации
- Task SerializeAsync(T obj, JsonSerializerOptions options): типизированная версия сериализует объект obj типа T и возвращает код json в виде строки.
- object Deserialize(string json, Type type, JsonSerializerOptions options): десериализует строку json в объект типа type и возвращает десериализованный объект. Последний необязательный параметр options позволяет задать дополнительные опции десериализации
- T Deserialize(string json, JsonSerializerOptions options): десериализует строку json в объект типа T и возвращает его.
- ValueTask DeserializeAsync(Stream utf8Json, Type type, JsonSerializerOptions options, CancellationToken token): десериализует текст UTF-8, который представляет объект JSON, в объект типа type. Последние два параметра необязательны: options позволяет задать дополнительные опции десериализации, а token устанавливает CancellationToken для отмены задачи. Возвращается десериализованный объект, обернутый в ValueTask
- ValueTask DeserializeAsync(Stream utf8Json, JsonSerializerOptions options, CancellationToken token): десериализует текст UTF-8, который представляет объект JSON, в объект типа T. Возвращается десериализованный объект, обернутый в ValueTask
Рассмотрим применение класса на простом примере. Сериализуем и десериализуем простейший объект:
- Здесь вначале сериализуем с помощью метода JsonSerializer.Serialize() объект типа Person в стоку с кодом json. Затем обратно получаем из этой строки объект Person посредством метода JsonSerializer.Deserialize().
Консольный вывод:
Хотя в примере выше сериализовался/десериализовался объект класса, но подобным способом мы также можем сериализовать/десериализовать структуры.
Некоторые замечания по сериализации/десериализации
- Объект, который подвергается десериализации, должен иметь конструктор без параметров. Например, в примере выше этот конструктор по умолчанию. Но можно также явным образом определить подобный конструктор в классе.
- Сериализации подлежат только публичные свойства объекта (с модификатором public).
Запись и чтение файла json
- Поскольку методы SerializeAsyc/DeserializeAsync могут принимать поток типа Stream, то соответственно мы можем использовать файловый поток для сохранения и последующего извлечения данных:
В данном случае вначале данные сохраняются в файл user.json и затем считываются из него.
Настройка сериализации с помощью JsonSerializerOptions
По умолчанию JsonSerializer сериализует объекты в минимифицированный код. С помощью дополнительного параметра типа JsonSerializerOptions можно настроить механизм сериализации/десериализации, используя свойства JsonSerializerOptions. Некоторые из его свойств:
- AllowTrailingCommas: устанавливает, надо ли добавлять после последнего элемента в json запятую. Если равно true, запятая добавляется
- IgnoreNullValues: устанавливает, будут ли сериализоваться/десериализоваться в json объекты и их свойства со значением null
- IgnoreReadOnlyProperties: аналогично устанавливает, будут ли сериализоваться свойства, предназначенные только для чтения
- WriteIndented: устанавливает, будут ли добавляться в json пробелы (условно говоря, для красоты). Если равно true устанавливаются дополнительные пробелы
Применение:
Консольный вывод:
Настройка сериализации с помощью атрибутов
- По умолчанию сериализации подлежат все публичные свойства. Кроме того, в выходном объекте json все названия свойств соответствуют названиям свойств объекта C#. Однако с помощью атрибутов JsonIgnore и JsonPropertyName.
- Атрибут JsonIgnore позволяет исключить из сериализации определенное свойство. А JsonPropertyName позволяет замещать оригинальное название свойства. Пример использования:
- В данном случае свойство Age будет игнорироваться, а для свойства Name будет использоваться псевдоним "firstname". Консольный вывод:
Обратите внимание, что, поскольку свойство Age не было сериализовано, то при десериализации для него используется значение по умолчанию.
Работа с XML в C# XML-документы
- На сегодняшний день XML является одним из распространенных стандартов документов, который позволяет в удобной форме сохранять сложные по структуре данные. Поэтому разработчики платформы .NET включили в фреймворк широкие возможности для работы с XML.
- Прежде чем перейти непосредственно к работе с XML-файлами, сначала рассмотрим, что представляет собой xml-документ и как он может хранить объекты, используемые в программе на c#.
Например, у нас есть следующий класс:
В программе на C# мы можем создать список объектов класса User:
Чтобы сохранить список в формате xml мы могли бы использовать следующий xml-файл:
- XML-документ объявляет строка . Она задает версию (1.0) и кодировку (utf-8) xml. Далее идет собственно содержимое документа.
- XML-документ должен иметь один единственный корневой элемент, внутрь которого помещаются все остальные элементы. В данном случае таким элементом является элемент . Внутри корневого элемента задан набор элементов . Вне корневого элемента мы не можем разместить элементы user.
- Каждый элемент определяется с помощью открывающего и закрывающего тегов, например, и , внутри которых помещается значение или содержимое элементов. Также элемент может иметь сокращенное объявление: - в конце элемента помещается слеш.
- Элемент может иметь вложенные элементы и атрибуты. В данном случае каждый элемент user имеет два вложенных элемента company и age и атрибут name.
- Атрибуты определяются в теле элемента и имеют следующую форму: название="значение". Например, , в данном случае атрибут называется name и имеет значение Bill Gates
- Внутри простых элементов помещается их значение. Например, Google - элемент company имеет значение Google.
- Названия элементов являются регистрозависимыми, поэтому и будут представлять разные элементы.
- Таким образом, весь список Users из кода C# сопоставляется с корневым элементом , каждый объект User - с элементом , а каждое свойство объекта User - с атрибутом или вложенным элементом элемента
- Что использовать для свойств - вложенные элементы или атрибуты? Это вопрос предпочтений - мы можем использовать как атрибуты, так и вложенные элементы. Так, в предыдущем примере вполне можно использовать вместо атрибута вложенный элемент:
Теперь рассмотрим основные подходы для работы с XML, которые имеются в C#.
Работа с XML с помощью классов System.Xml
Для работы с XML в C# можно использовать несколько подходов. В первых версиях фреймворка основной функционал работы с XML предоставляло пространство имен System.Xml. В нем определен ряд классов, которые позволяют манипулировать xml-документом:
- XmlNode: представляет узел xml. В качестве узла может использоваться весь документ, так и отдельный элемент
- XmlDocument: представляет весь xml-документ
- XmlElement: представляет отдельный элемент. Наследуется от класса XmlNode
- XmlAttribute: представляет атрибут элемента
- XmlText: представляет значение элемента в виде текста, то есть тот текст, который находится в элементе между его открывающим и закрывающим тегами
- XmlComment: представляет комментарий в xml
- XmlNodeList: используется для работы со списком узлов
Ключевым классом, который позволяет манипулировать содержимым xml, является XmlNode, поэтому рассмотрим некоторые его основные методы и свойства:
- Свойство Attributes возвращает объект XmlAttributeCollection, который представляет коллекцию атрибутов
- Свойство ChildNodes возвращает коллекцию дочерних узлов для данного узла
- Свойство HasChildNodes возвращает true, если текущий узел имеет дочерние узлы
- Свойство FirstChild возвращает первый дочерний узел
- Свойство LastChild возвращает последний дочерний узел
- Свойство InnerText возвращает текстовое значение узла
- Свойство InnerXml возвращает всю внутреннюю разметку xml узла
- Свойство Name возвращает название узла. Например, - значение свойства Name равно "user"
- Свойство ParentNode возвращает родительский узел у текущего узла
- Применим эти классы и их функционал. И вначале для работы с xml создадим новый файл. Назовем его users.xml и определим в нем следующее содержание:
Теперь пройдемся по этому документу и выведем его данные на консоль:
В итоге получился следующий вывод на консоли:
- Чтобы начать работу с документом xml, нам надо создать объект XmlDocument и затем загрузить в него xml-файл: xDoc.Load("users.xml");
- При разборе xml для начала мы получаем корневой элемент документа с помощью свойства xDoc.DocumentElement. Далее уже происходит собственно разбор узлов документа.
- В цикле foreach(XmlNode xnode in xRoot) пробегаемся по всем дочерним узлам корневого элемента. Так как дочерние узлы представляют элементы , то мы можем получить их атрибуты: XmlNode attr = xnode.Attributes.GetNamedItem("name"); и вложенные элементы: foreach(XmlNode childnode in xnode.ChildNodes)
- Чтобы определить, что за узел перед нами, мы можем сравнить его название: if(childnode.Name=="company")
Подобным образом мы можем создать объекты классов и структур по данным из xml:
- В данном случае определен класс User с тремя свойствами. При переборе узлов файла xml значения элементов и их атрибутов передается объекту класса User.
- Консольный вывод программы:
Изменение XML-документа
Для редактирования xml-документа (изменения, добавления, удаления элементов) мы можем воспользоваться методами класса XmlNode:
- AppendChild: добавляет в конец текущего узла новый дочерний узел
- InsertAfter: добавляет новый узел после определенного узла
- InsertBefore: добавляет новый узел до определенного узла
- RemoveAll: удаляет все дочерние узлы текущего узла
- RemoveChild: удаляет у текущего узла один дочерний узел и возвращает его
Класс XmlDocument добавляет еще ряд методов, которые позволяют создавать новые узлы:
- CreateNode: создает узел любого типа
- CreateElement: создает узел типа XmlDocument
- CreateAttribute: создает узел типа XmlAttribute
- CreateTextNode: создает узел типа XmlTextNode
- CreateComment: создает комментарий
- Возьмем xml-документ из прошлой темы и добавим в него новый элемент:
- Добавление элементов происходит по одной схеме. Сначала создаем элемент (xDoc.CreateElement("user")). Если элемент сложный, то есть содержит в себе другие элементы, то создаем эти элементы. Если элемент простой, содержащий внутри себя некоторое текстовое значение, то создаем этот текст (XmlText companyText = xDoc.CreateTextNode("Facebook");).
- Затем все элементы добавляются в основной элемент user, а тот добавляется в корневой элемент (xRoot.AppendChild(userElem);).
- Чтобы сохранить измененный документ на диск, используем метод Save: xDoc.Save("users.xml")
- После этого в xml-файле появится следующий элемент:
Удаление узлов
- Удалим первый узел xml-документа: