Как сериализовать объект 1С в JSON для передачи по HTTP и десериализовать его в базу с измененной структурой?

Программист 1С v8.3 (Управляемые формы) 1С:Документооборот IT и автоматизация бизнеса
← На главную

Обмен данными между информационными базами «1С:Предприятие» — одна из самых частых задач, с которыми сталкивается разработчик. Особенно интересной она становится, когда конфигурации-источник и конфигурация-приемник немного различаются (например, разные версии «1С:Документооборота» или наличие кастомных реквизитов в одной из баз). Попытка выполнить прямую десериализацию «в лоб» через СериализаторXDTO в таких случаях неминуемо приводит к аварийному завершению процесса из-за отсутствия некоторых свойств.

В этой статье мы подробно разберем, как правильно организовать обмен массивными документами через HTTP-сервисы, как обойти проблему несовпадения реквизитов метаданных, как передавать связанные ссылочные данные (например, ИНН контрагента вместо простого GUID) и как интегрировать в этот процесс механизмы Конвертации Данных.

Причина проблемы: почему падает прямая десериализация?

При использовании стандартного метода СериализаторXDTO.ПрочитатьJSON() платформа ожидает полного соответствия типов и свойств, описанных в схеме XML/XDTO, текущим метаданным конфигурации. Если в файле JSON присутствует реквизит, которого нет в объекте-приемнике, или отсутствует обязательное свойство, платформа генерирует исключение «Ошибка преобразования данных XDTO».

Давайте проанализируем, какими путями мы можем решить эту задачу и сделать наш обмен гибким и отказоустойчивым.

Решение 1. Десериализация в промежуточную структуру (Лучшая практика)

Самый надежный и контролируемый способ десериализовать объект в отличающуюся конфигурацию — отказаться от немедленного создания прикладного объекта. Вместо этого мы сначала считываем весь входящий поток JSON в универсальную коллекцию 1С — Структура (или Соответствие). Это позволяет нам программно «вычистить» или трансформировать данные до того, как они будут записаны в базу данных.

Рассмотрим по шагам данный алгоритм:

  1. Считываем входящий JSON-пакет в Структуру с помощью встроенных средств платформы.
  2. Создаем новый документ (или справочник) в базе-приемнике.
  3. Программно сопоставляем ключи полученной структуры с реквизитами метаданных целевого объекта. Убираем всё лишнее.
  4. Заполняем реквизиты объекта и записываем его.

Посмотрим на пример кода, реализующий этот подход:


// На стороне приемника в HTTP-сервисе принимаем JSON-строку
Функция ПолучитьОбъектИзJSON(ТекстJSON)
    
    // Шаг 1. Читаем JSON в структуру
    ЧтениеJSON = Новый ЧтениеJSON();
    ЧтениеJSON.УстановитьСтроку(ТекстJSON);
    
    ДанныеСтруктуры = ПрочитатьJSON(ЧтениеJSON);
    ЧтениеJSON.Закрыть();
    
    // Шаг 2. Создаем объект-приемник
    НовыйДокумент = Документы.ВнутреннийДокумент.СоздатьДокумент();
    
    // Шаг 3. Безопасное заполнение реквизитов
    // Метаданные целевого объекта
    МетаданныеОбъекта = НовыйДокумент.Метаданные();
    
    Для Каждого ЭлементДанных Из ДанныеСтруктуры Цикл
        ИмяРеквизита = ЭлементДанных.Ключ;
        ЗначениеРеквизита = ЭлементДанных.Виз;
        
        // Проверяем, существует ли такой реквизит в нашей базе-приемнике
        Если МетаданныеОбъекта.Реквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
            
            // Если реквизит ссылочный, обрабатываем сопоставление (например, поиск по GUID)
            ТипРеквизита = МетаданныеОбъекта.Реквизиты[ИмяРеквизита].Тип;
            
            Если ТипРеквизита.СодержитТип(Тип("СправочникСсылка.Контрагенты")) Тогда
                // Выполняем поиск по УИД
                СсылкаКонтрагента = Справочники.Контрагенты.ПолучитьСсылку(Новый УникальныйИдентификатор(ЗначениеРеквизита));
                НовыйДокумент[ИмяРеквизита] = СсылкаКонтрагента;
            Иначе
                НовыйДокумент[ИмяРеквизита] = ЗначениеРеквизита;
            КонецЕсли;
            
        КонецЕсли;
    КонецЦикла;
    
    // Заполняем стандартные реквизиты (Дата, Номер и т.д.)
    ЗаполнитьЗначенияСвойств(НовыйДокумент, ДанныеСтруктуры, "Дата, Номер, Содержание");
    
    Попытка
        НовыйДокумент.Записать();
        Возврат Истина;
    Исключение
        ЗаписатьЛогТранзакции(ОписаниеОшибки());
        Возврат Ложь;
    КонецПопытки;
    
КонецФункции

Решение 2. Использование функций восстановления в ПрочитатьJSON

Если вы всё же хотите использовать метод ПрочитатьJSON(), встроенный механизм платформы позволяет перехватить процесс десериализации свойств с помощью специальной функции восстановления. Мы можем указать массив свойств, которые требуют ручного разбора.

Синтаксис метода выглядит так:

ПрочитатьJSON(ЧтениеJSON, Тип, ИмяФункцииВосстановления, МодульФункцииВосстановления, ДополнительныеПараметрыФункцииВосстановления, ТипыДляОбработкиВосстановления, ИменаСвойствДляВосстановления)

Рассмотрим пример настройки такого чтения:


// Настраиваем параметры десериализации
ИменаСвойствДляОбработки = Новый Массив();
ИменаСвойствДляОбработки.Добавить("Контрагент");
ИменаСвойствДляОбработки.Добавить("УдаленныйРеквизитКоторогоНет"); // Элемент, который может вызвать ошибку

Данные = ПрочитатьJSON(ЧтениеJSON, Ложь, "ВосстановитьСвойстваОбъекта", ЭтотОбъект,, ИменаСвойствДляОбработки);

И сама функция восстановления, которая должна находиться в экспортном методе модуля:


Функция ВосстановитьСвойстваОбъекта(Свойство, Значение, ДополнительныеПараметры) Экспорт
    
    Если Свойство = "УдаленныйРеквизитКоторогоНет" Тогда
        // Просто игнорируем его или возвращаем Неопределено, предотвращая падение
        Возврат Неопределено;
    КонецЕсли;
    
    Если Свойство = "Контрагент" Тогда
        // Здесь мы можем вручную превратить полученную строку-GUID в полноценную ссылку
        Попытка
            УИД = Новый УникальныйИдентификатор(Значение);
            Возврат Справочники.Контрагенты.ПолучитьСсылку(УИД);
        Исключение
            Возврат Справочники.Контрагенты.ПустаяСсылка();
        КонецПопытки;
    КонецЕсли;
    
    Возврат Значение;
    
КонецФункции

Решение 3. Проблема передачи ссылок (Как передать ИНН контрагента для поиска?)

При стандартной сериализации объектов через Сериализатор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"
      }
    ]
  }
}

Алгоритм обработки такого конверта на приемнике:

  1. Читаем массив СвязанныеДанные.Справочник_Контрагенты.
  2. Для каждого элемента проверяем наличие контрагента в базе. Сначала ищем по UID. Если не нашли — ищем по ИНН/КПП.
  3. Если нашли по ИНН, но UID отличается — привязываем к существующему (или обновляем UID, в зависимости от бизнес-логики). Если вообще не нашли — создаем нового контрагента с переданным UID.
  4. После того как все связанные справочники созданы и обновлены, спокойно десериализуем и записываем основной Документ. Все ссылки гарантированно сопоставятся.

Решение 4. Как прикрутить Конвертацию Данных к HTTP-обмену?

Разработчики часто задаются вопросом: можно ли использовать правила конвертации данных (КД) при обмене через HTTP-сервисы, чтобы не писать разбор JSON руками? Да, это возможно и является стандартом для крупных систем.

Здесь есть два пути в зависимости от версии Конвертации Данных:

Использование Конвертации Данных 2.1 (КД 2)

КД 2 исторически генерирует правила для формата XML. Если вы хотите использовать ее для HTTP-сервиса:

  1. HTTP-сервис на стороне источника формирует XML по правилам обмена (используя стандартную обработку выгрузки).
  2. Этот XML-текст передается в теле POST-запроса на HTTP-сервис приемника.
  3. Приемник принимает XML-строку и вызывает код загрузки данных КД2 (через БСП или программно инициализируя обработку УниверсальныйОбменДаннымиXML), передавая туда полученный поток.

Примечание: Если вам принципиально использовать формат JSON, вы можете на источнике перекодировать выгруженный XML в JSON с помощью функции преобразования (пример такой функции приводится в сообщении 6 темы форума), а на приемнике выполнить обратную процедуру. Однако для КД 2 проще передавать чистый XML.

Использование Конвертации Данных 3.0 (КД 3) и EnterpriseData

Это самый современный и технологичный стек в экосистеме 1С. КД 3 полностью базируется на формате EnterpriseData, который основан на XDTO-пакетах конфигурации.

Решение 5. Исправление ошибки импорта схем XDTO (anyType)

В процессе экспериментов с 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, что ломает валидацию.

Как исправить ошибку:

  1. Если схемы, которые вы импортируете, уже добавлены в метаданные вашей конфигурации как «XDTO-пакеты», то не создавайте фабрику программно! Используйте готовую глобальную фабрику ФабрикаXDTO, в которой уже зарегистрированы все схемы конфигурации.
  2. Если фабрику необходимо создать динамически, передавайте в конструктор массив всех необходимых моделей схем (включая импортируемые базовые схемы 1С):

МассивМоделей = Новый Массив();
МассивМоделей.Добавить(МодельБазовыхТипов);
МассивМоделей.Добавить(МодельОсновногоПакета);

НоваяФабрика = Новый ФабрикаXDTO(МассивМоделей);

Резюме

При организации обмена по JSON между отличающимися базами 1С придерживайтесь следующих правил:

← На главную