При подготовке иерархических данных (например, справочника «Номенклатура» с произвольной вложенностью) к передаче во внешние системы или сериализации в формат JSON часто возникает необходимость обхода дерева значений с его конвертацией в контейнеры 1С. В процессе разработки рекурсивной функции разработчики часто сталкиваются с проблемой: объект Структура затирает ранее внесенные элементы, если они находятся на одном уровне иерархии или привязываются к одному и тому же родительскому ключу.
Рассмотрим, почему возникает эта проблема, как устроена работа с иерархическими контейнерами данных в 1С и какими способами можно корректно преобразовать дерево в формат, готовый к выгрузке в JSON.
Объект Структура в платформе 1С представляет собой коллекцию пар «Ключ - Значение». Главное ограничение этого объекта заключается в том, что имена ключей должны быть уникальными и соответствовать правилам именования идентификаторов. При попытке вызвать метод Вставить() с уже существующим ключом старое значение не добавляется рядом, а полностью перезаписывается новым.
Если несколько элементов справочника или дерева значений имеют одного родителя, запись их в единый родительский ключ структуры приводит к постоянной перезаписи данных. В конечном объекте остается только последняя обработанная строка.
Чтобы избежать перезаписи узлов одного уровня, разберем базовый подход: представление каждого узла иерархии в виде отдельной структуры, а списка подчиненных элементов — в виде массива таких структур.
Проанализируем структуру будущего объекта:
Структура. В ней содержатся реквизиты (например, Код, Наименование, ЭтоГруппа).Элементы (или Содержимое), значением которого является Массив.Посмотрим на пример реализации рекурсивной функции на сервере для объекта ДеревоЗначений:
Функция ПреобразоватьСтрокиДереваВМассив(КоллекцияСтрок)
РезультатМассив = Новый Массив;
Для Каждого СтрокаДерева Из КоллекцияСтрок Цикл
ЭлементСтруктура = Новый Структура;
ЭлементСтруктура.Вставить("Код", СтрокаДерева.Код);
ЭлементСтруктура.Вставить("Наименование", СтрокаДерева.Наименование);
ЭлементСтруктура.Вставить("ЭтоГруппа", СтрокаДерева.ЭтоГруппа);
// Получаем дочерние строки
ПодчиненныеСтроки = СтрокаДерева.Строки;
Если ПодчиненныеСтроки.Количество() > 0 Тогда
// Рекурсивный вызов для вложенных уровней
ВложенныйМассив = ПреобразоватьСтрокиДереваВМассив(ПодчиненныеСтроки);
ЭлементСтруктура.Вставить("Элементы", ВложенныйМассив);
КонецЕсли;
РезультатМассив.Добавить(ЭлементСтруктура);
КонецЦикла;
Возврат РезультатМассив;
КонецФункции
После получения такого массива его легко сериализовать в JSON с помощью штатной функции ЗаписатьJSON():
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
// Записываем сформированный массив структур
ЗаписатьJSON(ЗаписьJSON, СформированныйМассив);
СтрокаJSON = ЗаписьJSON.Закрыть();
При таком подходе итоговый JSON будет содержать валидную древовидную структуру вида [{"Код": "001", "Элементы": [...]}], которая легко разбирается на стороне любого внешнего сервиса аналогичной рекурсивной функцией.
Если дерево значений имеет очень большой объем и создание промежуточных объектов 1С (сотни тысяч структур и массивов) забивает оперативную память, проанализируем альтернативный вариант — прямое формирование JSON с помощью объекта ЗаписьJSON.
Разберем по шагам данный подход:
УстановитьСтроку() или ОткрытьФайл().ЗаписатьНачалоМассива().ЗаписатьНачалоОбъекта() и записываем его свойства через ЗаписатьИмяСвойства() и ЗаписатьЗначение().Элементы.ЗаписатьКонецОбъекта() и ЗаписатьКонецМассива().Рассмотрим пример кода потоковой записи:
Процедура ЗаписатьДеревоВJSONРекурсивно(ЗаписьJSON, КоллекцияСтрок)
ЗаписьJSON.ЗаписатьНачалоМассива();
Для Каждого СтрокаДерева Из КоллекцияСтрок Цикл
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("Код");
ЗаписьJSON.ЗаписатьЗначение(СтрокаДерева.Код);
ЗаписьJSON.ЗаписатьИмяСвойства("Наименование");
ЗаписьJSON.ЗаписатьЗначение(СтрокаДерева.Наименование);
ПодчиненныеСтроки = СтрокаДерева.Строки;
Если ПодчиненныеСтроки.Количество() > 0 Тогда
ЗаписьJSON.ЗаписатьИмяСвойства("Элементы");
ЗаписатьДеревоВJSONРекурсивно(ЗаписьJSON, ПодчиненныеСтроки);
КонецЕсли;
ЗаписьJSON.ЗаписатьКонецОбъекта();
КонецЦикла;
ЗаписьJSON.ЗаписатьКонецМассива();
КонецПроцедуры
При написании процедур обхода иерархических данных обратим внимание на различия в типах объектов платформы 1С:
ДеревоЗначений. Доступ к дочерним узлам строки выполняется через свойство СтрокаДерева.Строки.ДанныеФормыДерево и его элементы ДанныеФормыДеревоЭлемент. Для получения подчиненных элементов применяется метод ПолучитьЭлементы().Учитывайте этот нюанс при переносе кода между клиентским и серверным контекстом исполняемого модуля.