diamond АШ Tlg

Как сериализовать и десериализовать данные из 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.Закрыть();
Поток.Закрыть();