Как сериализовать и десериализовать данные из JSON на платформе 1С 8.3
Место JSON в 1С
JSON это популярный формат представления данных в структурированном текстовом формате. Когда-то очень давно данные между системами передавали в простом текстовом формате CSV, ограниченный плоскими таблицами, и который до сих пор доступен в программах типа Excel. Хотя SGML и существовал с 1969 года, настоящую революцию произвёл его производный формат XML, позволив передавать произвольные структурированные данные в человеко- и машиночитаемом формате. Но формат XML оказался сильно избыточным - в нём очень много букв и данные могли занимать огромное место на диске, поэтому был изобретён более "экономный" JSON. Но и на этом прогресс не стоял на месте, конкуренты придумали YAML, который на сегодня и можно считать наиболее актуальным и прогрессивным форматом сериализации структурированных данных.
Платформа 1С 8.3 изначально на фундаментальном уровне заложилась в XML, хотя на момент внедрения уже было ясно что XML имеет вышеописанные недостатки и быстро устаревает. С сильным опозданием была добавлена в платформу поддержка формата JSON. К сожалению, про YAML пока даже и речи нет. Данная статья существует только из-за того, что реализация формата JSON разбросана по нескольким объектам и процедурам глобального контекста и всё это не собрано в единый полнофункциональный объект платформы. Более того, во встроенной помощи информация также фрагментирована и собрать всё воедино представляет некоторую сложность, если не работать с JSON каждый день.
Способ 1. Сериализация и десериализация структуры или массива в строку JSON и обратно
Данный способ по сути является низкоуровневым и подходит для онлайн-интеграций для обмена небольшими порциями произвольных данных между разнородными системами, например через шину ESB или REST API. Доступен начиная с платформы 8.3.6.
В общем случае, для сериализации данных, которые вы собираетесь отправить в обмен, требуется сперва Записать их в Структуру произвольной вложенности, в которой допустимы следующие типы значений полей: Строка, Число, Булево, Дата, Массив, ФиксированныйМассив, Структура, ФиксированнаяСтруктура, Соответствие, ФиксированноеСоответствие. Бинарные данные нужно преобразовывать в строку в формате Base64. Вместо Структуры в качестве корневого элемента можно использовать любой вышеперечисленный тип, но удобнее и гибче использовать Структуру.
Для полей типа Дата/Время в JSON отсутствуют какие-либо стандарты и они передаются как строка произвольного формата. В примерах ниже используется формат даты ISO, но вы можете использовать любой другой, но тогда декодирование даты придётся писать отдельно.
Функция СериализоватьВJSON(Значение) Экспорт
Настройки = Новый НастройкиСериализацииJSON();
Настройки.ФорматСериализацииДаты = ФорматДатыJSON.ISO;
Настройки.ВариантЗаписиДаты = ВариантЗаписиДатыJSON.ЛокальнаяДата;
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Значение, Настройки);
СтрокаJSON = ЗаписьJSON.Закрыть();
Возврат СтрокаJSON;
КонецФункции
Десериализация данных из строки JSON
При получении данных можно их восстановить обратно в тот объект, который сериализовали в процедуре выше. При десериализации основная сложность заключается в распознавании полей типа Дата. Функция глобального контекста ПрочитатьJSON не умеет этого делать и ему нужно давать подсказку, например "Период,ДатаНачала". Эти поля необязательно должны существовать в пакете JSON, поэтому для универсальной функции можно передать вообще полный словарь возможных имен полей типа Дата на все случаи жизни:
Функция ДесериализоватьИзJSON(СтрокаJSON, ПоляДата = "") Экспорт
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
Значение = ПрочитатьJSON(ЧтениеJSON,, ПоляДата, ФорматДатыJSON.ISO);
ЧтениеJSON.Закрыть();
Возврат Значение;
КонецФункции
Для успешной десериализации Структуры наименования полей в JSON должны соответствовать требованиям к значению ключа для Структуры!
Способ 2. Сериализация и восстановление ссылочного объекта платформы 1С через СериализаторXDTO
Данный способ является высокоуровневым и наилучшим образом подходит для выгрузки и загрузки данных между базами 1С с идентичными конфигурациями, например для восстановления удалённого элемента справочника из резервной копии в рабочую. Главное требование - ссылочный объект должен иметь свойство сериализоваться (что всегда указывается в синтаксис-помощнике). Во-вторых, для выгрузки всех полей и табличный частей это должен быть загруженный объект, а не ссылка. Доступен начиная с платформы 8.3.7.
Функция СериализоватьВJSON(Значение) Экспорт
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(, Символы.Таб));
СериализаторXDTO.ЗаписатьJSON(ЗаписьJSON, Значение.ПолучитьОбъект(), НазначениеТипаXML.Явное);
//СериализаторXDTO.ЗаписатьJSON(ЗаписьJSON, Значение, НазначениеТипаXML.Явное); // сериализуется только ссылка
СтрокаJSON = ЗаписьJSON.Закрыть();
Возврат СтрокаJSON;
КонецФункции
В результате сформируется JSON с определенной 1С структурой, например такой:
{
"#type": "jcfg:CatalogObject.Подразделения",
"#value": {
"Ref": "1a032398-58ba-11ef-9be3-00e93af6730b",
"DeletionMark": false,
"Code": "000000001",
"Description": "HR",
"Архив": false,
"ФОТ": 512900.51,
"Организация": "ae6f08c6-589c-11ef-923e-00e93af6730b",
"ДопДанные": [
{
"Реквизит1": "Зеленый",
"Вкл": true
},
{
"Реквизит1": "Коричневый",
"Вкл": false
}
]
}
}
Данные в таком формате довольно просто загружаются в идентичную конфигурацию следующим образом:
Функция ДесериализоватьИзJSON(СтрокаJSON) Экспорт
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
ЗначениеОбъект = СериализаторXDTO.ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// можно записать полученный объект в базу:
ЗначениеОбъект.ОбменДанными.Загрузка = Истина;
ЗначениеОбъект.Записать();
КонецФункции
Хотя, в общем-то, мало что мешает использовать данный формат для загрузки и в чужеродной системе, но придется разбирать строку JSON вручную, и каким-то образом побороть проблему с составными типами полей.
Сериализация и восстановление больших объёмов данных
Если объём загружаемых/выгружаемых данных не помещается в оперативной памяти, или занимает существенную её часть, то следует применять потоки. Под большим объёмом данных обычно подразумевается некая структура JSON, которая будет содержать массив с большим числов элементов. Обычное такое бывает на проектах по внедрению при переносе данных из старой системы в новую.
Рассмотрим для начала ПотокВПамяти. Данный объект, как понятно из названия, занимает оперативную память и проблему нехватки памяти он не решает, но помогает сэкономить при формировании JSON. Его можно использовать для оптимизации нагрузки на сервер при интеграциях через шину ESB и http-сервисы, когда памяти достаточно. Пример использования:
Поток = Новый ПотокВПамяти;
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ПроверятьСтруктуру = Ложь; // 'Истина' только во время отладки
ЗаписьJSON.ОткрытьПоток(Поток,,, Новый ПараметрыЗаписиJSON(, Символы.Таб));
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("МоиДанные");
ЗаписьJSON.ЗаписатьНачалоМассива();
//Для Каждого Ссылка Из МассивДанных Цикл // так не надо делать!
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СериализаторXDTO.ЗаписатьJSON(ЗаписьJSON, Выборка.Ссылка.ПолучитьОбъект(), НазначениеТипаXML.Явное);
// Либо ЗаписатьJSON(ЗаписьJSON,...) для низкоуровневой сериализации по способу №1
КонецЦикла;
ЗаписьJSON.ЗаписатьКонецМассива();
ЗаписьJSON.ЗаписатьКонецОбъекта();
ЗаписьJSON.Закрыть();
СтрокаJSON = ПолучитьСтрокуИзДвоичныхДанных(Поток.ЗакрытьИПолучитьДвоичныеДанные());
Данный код возвратит такую структуру JSON:
{
"МоиДанные": [
// сериализованные объекты из переданного в функцию массива
]
}
Здесь важно отметить, что для обхода данных в цикле не стоит предварительно напихивать все обрабатываемые данные в массив или таблицу значений, иначе никакой оптимизации памяти не будет. Особенно это касается низкоуровневого способа через сериализацию структур.
Теперь рассмотрим ситуацию нехватки памяти. В этом случае нужно писать в файл, и поможет нам в этом ФайловыйПоток. Код остаётся тот же самый что и выше, только поменяем тип потока:
Поток = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
...
ЗаписьJSON.Закрыть();
Поток.Закрыть();
Потоковое чтение из файла и десериализация станут для разработчика настоящим челленджем! Ко всеобщей печали, ЧтениеJSON не умеет считывать из потока объекты, а только строки. Каждую такую строку JSON придётся разбирать вручную. Пример для десериализации файла такого формата:
{
"МоиДанные": [
{
"Товар": "Гайка М8",
"Цена": 100
},
{
"Товар": "Болт М10",
"Цена": 120.04
},
{
"Товар": "Ключ разводной",
"Цена": 560
}
]
}
Поток = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение);
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.ОткрытьПоток(Поток);
Пока ЧтениеJSON.Прочитать() Цикл
Если ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.ИмяСвойства И ЧтениеJSON.ТекущееЗначение = "МоиДанные" Тогда
ЧтениеJSON.Прочитать(); // начало массива [
Пока ЧтениеJSON.Прочитать() И ЧтениеJSON.ТипТекущегоЗначения <> ТипЗначенияJSON.КонецМассива Цикл
Данные = Новый Структура; // сюда собираем реквизиты
// Товар:
ЧтениеJSON.Прочитать();
Ключ = ЧтениеJSON.ТекущееЗначение;
ЧтениеJSON.Прочитать();
Значение = ЧтениеJSON.ТекущееЗначение;
Данные.Вставить(Ключ, Значение);
// Цена:
ЧтениеJSON.Прочитать();
Ключ = ЧтениеJSON.ТекущееЗначение;
ЧтениеJSON.Прочитать();
Значение = ЧтениеJSON.ТекущееЗначение;
Данные.Вставить(Ключ, Значение);
// Здесь сохраняем каким-нибудь образом Данные в базе
ЧтениеJSON.Прочитать(); // конец объекта }
КонецЦикла;
КонецЕсли;
КонецЦикла;
ЧтениеJSON.Закрыть();
Поток.Закрыть();