Обмен данными между информационными базами «1С:Предприятие» — одна из самых частых задач, с которыми сталкивается разработчик. Особенно интересной она становится, когда конфигурации-источник и конфигурация-приемник немного различаются (например, разные версии «1С:Документооборота» или наличие кастомных реквизитов в одной из баз). Попытка выполнить прямую десериализацию «в лоб» через СериализаторXDTO в таких случаях неминуемо приводит к аварийному завершению процесса из-за отсутствия некоторых свойств.
В этой статье мы подробно разберем, как правильно организовать обмен массивными документами через HTTP-сервисы, как обойти проблему несовпадения реквизитов метаданных, как передавать связанные ссылочные данные (например, ИНН контрагента вместо простого GUID) и как интегрировать в этот процесс механизмы Конвертации Данных.
При использовании стандартного метода СериализаторXDTO.ПрочитатьJSON() платформа ожидает полного соответствия типов и свойств, описанных в схеме XML/XDTO, текущим метаданным конфигурации. Если в файле JSON присутствует реквизит, которого нет в объекте-приемнике, или отсутствует обязательное свойство, платформа генерирует исключение «Ошибка преобразования данных XDTO».
Давайте проанализируем, какими путями мы можем решить эту задачу и сделать наш обмен гибким и отказоустойчивым.
Самый надежный и контролируемый способ десериализовать объект в отличающуюся конфигурацию — отказаться от немедленного создания прикладного объекта. Вместо этого мы сначала считываем весь входящий поток JSON в универсальную коллекцию 1С — Структура (или Соответствие). Это позволяет нам программно «вычистить» или трансформировать данные до того, как они будут записаны в базу данных.
Рассмотрим по шагам данный алгоритм:
Структуру с помощью встроенных средств платформы.Посмотрим на пример кода, реализующий этот подход:
// На стороне приемника в HTTP-сервисе принимаем JSON-строку
Функция ПолучитьОбъектИзJSON(ТекстJSON)
// Шаг 1. Читаем JSON в структуру
ЧтениеJSON = Новый ЧтениеJSON();
ЧтениеJSON.УстановитьСтроку(ТекстJSON);
ДанныеСтруктуры = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Шаг 2. Создаем объект-приемник
НовыйДокумент = Документы.ВнутреннийДокумент.СоздатьДокумент();
// Шаг 3. Безопасное заполнение реквизитов
// Метаданные целевого объекта
МетаданныеОбъекта = НовыйДокумент.Метаданные();
Для Каждого ЭлементДанных Из ДанныеСтруктуры Цикл
ИмяРеквизита = ЭлементДанных.Ключ;
ЗначениеРеквизита = ЭлементДанных.Виз;
// Проверяем, существует ли такой реквизит в нашей базе-приемнике
Если МетаданныеОбъекта.Реквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
// Если реквизит ссылочный, обрабатываем сопоставление (например, поиск по GUID)
ТипРеквизита = МетаданныеОбъекта.Реквизиты[ИмяРеквизита].Тип;
Если ТипРеквизита.СодержитТип(Тип("СправочникСсылка.Контрагенты")) Тогда
// Выполняем поиск по УИД
СсылкаКонтрагента = Справочники.Контрагенты.ПолучитьСсылку(Новый УникальныйИдентификатор(ЗначениеРеквизита));
НовыйДокумент[ИмяРеквизита] = СсылкаКонтрагента;
Иначе
НовыйДокумент[ИмяРеквизита] = ЗначениеРеквизита;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Заполняем стандартные реквизиты (Дата, Номер и т.д.)
ЗаполнитьЗначенияСвойств(НовыйДокумент, ДанныеСтруктуры, "Дата, Номер, Содержание");
Попытка
НовыйДокумент.Записать();
Возврат Истина;
Исключение
ЗаписатьЛогТранзакции(ОписаниеОшибки());
Возврат Ложь;
КонецПопытки;
КонецФункции
Если вы всё же хотите использовать метод ПрочитатьJSON(), встроенный механизм платформы позволяет перехватить процесс десериализации свойств с помощью специальной функции восстановления. Мы можем указать массив свойств, которые требуют ручного разбора.
Синтаксис метода выглядит так:
ПрочитатьJSON(ЧтениеJSON, Тип, ИмяФункцииВосстановления, МодульФункцииВосстановления, ДополнительныеПараметрыФункцииВосстановления, ТипыДляОбработкиВосстановления, ИменаСвойствДляВосстановления)
Рассмотрим пример настройки такого чтения:
// Настраиваем параметры десериализации
ИменаСвойствДляОбработки = Новый Массив();
ИменаСвойствДляОбработки.Добавить("Контрагент");
ИменаСвойствДляОбработки.Добавить("УдаленныйРеквизитКоторогоНет"); // Элемент, который может вызвать ошибку
Данные = ПрочитатьJSON(ЧтениеJSON, Ложь, "ВосстановитьСвойстваОбъекта", ЭтотОбъект,, ИменаСвойствДляОбработки);
И сама функция восстановления, которая должна находиться в экспортном методе модуля:
Функция ВосстановитьСвойстваОбъекта(Свойство, Значение, ДополнительныеПараметры) Экспорт
Если Свойство = "УдаленныйРеквизитКоторогоНет" Тогда
// Просто игнорируем его или возвращаем Неопределено, предотвращая падение
Возврат Неопределено;
КонецЕсли;
Если Свойство = "Контрагент" Тогда
// Здесь мы можем вручную превратить полученную строку-GUID в полноценную ссылку
Попытка
УИД = Новый УникальныйИдентификатор(Значение);
Возврат Справочники.Контрагенты.ПолучитьСсылку(УИД);
Исключение
Возврат Справочники.Контрагенты.ПустаяСсылка();
КонецПопытки;
КонецЕсли;
Возврат Значение;
КонецФункции
При стандартной сериализации объектов через СериализаторXDTO все ссылочные типы выгружаются в JSON в виде плоских строк-идентификаторов (UUID). Но что делать, если в базе-приемнике этого контрагента еще нет, и нам нужно создать его? Для создания контрагента одного GUID недостаточно — требуются его ИНН, КПП и Наименование.
Существует два подхода к решению этой проблемы.
В профессиональной разработке не рекомендуется «разворачивать» контрагента внутри структуры самого документа. Представьте, что в документе у вас 10 строк, и в каждой строке фигурирует один и тот же контрагент. Если вы поместите все его реквизиты (ИНН, Наименование, Адрес) внутрь каждой строки, размер JSON-файла вырастет лавинообразно.
Вместо этого мы спроектируем пакет обмена в виде «конверта», где на верхнем уровне передается сам документ (со ссылками в виде UUID), а рядом передается массив всех связанных справочников, которые задействованы в этом документе.
Пример структуры JSON-конверта:
{
"Документ": {
"Номер": "ВН-0021",
"Дата": "2023-10-25T12:00:00",
"Контрагент": "cc7a4214-1ee0-11e9-8125-00155d1a7e04"
},
"СвязанныеДанные": {
"Справочник_Контрагенты": [
{
"UID": "cc7a4214-1ee0-11e9-8125-00155d1a7e04",
"Наименование": "ООО МегаТрейд",
"ИНН": "7701234567",
"КПП": "770101001"
}
]
}
}
Алгоритм обработки такого конверта на приемнике:
СвязанныеДанные.Справочник_Контрагенты.Документ. Все ссылки гарантированно сопоставятся.Разработчики часто задаются вопросом: можно ли использовать правила конвертации данных (КД) при обмене через HTTP-сервисы, чтобы не писать разбор JSON руками? Да, это возможно и является стандартом для крупных систем.
Здесь есть два пути в зависимости от версии Конвертации Данных:
КД 2 исторически генерирует правила для формата XML. Если вы хотите использовать ее для HTTP-сервиса:
УниверсальныйОбменДаннымиXML), передавая туда полученный поток.Примечание: Если вам принципиально использовать формат JSON, вы можете на источнике перекодировать выгруженный XML в JSON с помощью функции преобразования (пример такой функции приводится в сообщении 6 темы форума), а на приемнике выполнить обратную процедуру. Однако для КД 2 проще передавать чистый XML.
Это самый современный и технологичный стек в экосистеме 1С. КД 3 полностью базируется на формате EnterpriseData, который основан на XDTO-пакетах конфигурации.
EnterpriseDataExchange).В процессе экспериментов с XDTO автор темы столкнулся с ошибкой валидации при попытке прочитать XML-схему (XSD) конфигурации-получателя:
Несоответствие типов XDTO: Тип '{http://www.w3.org/2001/XMLSchema}anyType' не соответствует типу ''
Выясним причину этой ошибки. При экспорте схемы из дерева метаданных 1С генерирует XML-схему, которая содержит тег <import namespace="...">. Этот тег указывает, что схема ссылается на базовые типы данных 1С (data core, enterprise types и др.).
Когда вы пишите следующий код:
ОбъектModel = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
НоваяФабрика = Новый ФабрикаXDTO(ОбъектModel);
Новая фабрика инициализируется только ОДНОЙ схемой и ничего не знает о схемах импортируемых пространств имен. В результате типы, унаследованные от импортируемых схем, сбрасываются системой в базовый anyType, что ломает валидацию.
Как исправить ошибку:
ФабрикаXDTO, в которой уже зарегистрированы все схемы конфигурации.
МассивМоделей = Новый Массив();
МассивМоделей.Добавить(МодельБазовыхТипов);
МассивМоделей.Добавить(МодельОсновногоПакета);
НоваяФабрика = Новый ФабрикаXDTO(МассивМоделей);
При организации обмена по JSON между отличающимися базами 1С придерживайтесь следующих правил: