При интеграции различных информационных баз на платформе 1С:Предприятие разработчики часто сталкиваются с задачей организации двустороннего обмена данными. Если передача простых типов (таких как строки, числа или булево) через Web-сервисы (SOAP) не вызывает затруднений, то при попытке отправить сложные структуры данных — например, ТаблицаЗначений или Структура, внутри которой находится еще одна таблица — система выдает ошибки несоответствия типов.
В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем ограничения стандартных механизмов платформы и рассмотрим лучшие современные практики решения этой задачи с готовыми примерами кода.
Web-сервисы работают по строгому протоколу SOAP, где структура передаваемых данных описывается схемой XML/XDTO (файл WSDL). Тип данных ТаблицаЗначений является внутренним мутабельным типом платформы 1С. В глобальных стандартах XML нет прямого аналога для 1С-овской таблицы значений. Пытаясь передать объект ТаблицаЗначений напрямую в параметр метода Web-сервиса, мы неизбежно получим системную ошибку несоответствия типов.
Дополнительная сложность заключается в ссылочных типах данных. Если строки вашей таблицы содержат ссылки на элементы справочников или документов (например, СправочникСсылка.Регионы), то простая передача этих ссылок в другую базу бессмысленна. В базе-приемнике эти уникальные идентификаторы (GUID) либо будут отсутствовать (что приведет к появлению «битых» ссылок со значением «Объект не найден»), либо будут указывать на совершенно другие объекты, если конфигурации баз различаются.
Прежде чем перейти к рекомендуемому решению, разберем методы, которые часто советуют на форумах, и выясним, почему их использование в реальных проектах нежелательно:
ЗначениеВСтрокуВнутр() и ЗначениеИзСтрокиВнутр(): Этот способ преобразует объект 1С во внутренний системный формат строки. Он кажется простым, но имеет критический недостаток. Этот механизм работает исключительно в рамках абсолютно идентичных конфигураций и версий платформ. Если база-источник и база-приемник хоть немного отличаются по структуре метаданных, попытка десериализации гарантированно вызовет ошибку.СериализаторXDTO: Позволяет быстро получить XML-представление объекта. Однако, если конфигурации баз различаются (например, разные имена справочников или реквизитов), метод ПрочитатьXML() на стороне приемника завершится аварийно.Использование формата JSON — это стандарт де-факто для обмена данными. JSON представляет собой легкий текстовый формат, не привязанный к конкретной конфигурации 1С. Он позволяет передавать структуры практически любой вложенности. Для автоматизации и быстрого построения такого обмена подойдет конструктор API для интеграции 1С в формате JSON.
Для успешной передачи таблицы значений через JSON нам необходимо выполнить три шага:
Массив, состоящий из объектов типа Структура.Рассмотрим детально, как реализовать этот алгоритм на практике.
Допустим, нам нужно передать структуру, содержащую некоторую служебную информацию и таблицу с данными регионов. Напишем функцию, которая подготавливает данные, переводит их в JSON и отправляет через Web-сервис.
&НаСервере
Функция ОтправитьДанныеЧерезВебСервис()
// 1. Формируем исходную таблицу значений (например, запросом)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Регионы.Ссылка КАК Ссылка,
| Регионы.Код КАК Код,
| Регионы.Наименование КАК Наименование,
| Регионы.КодРегиона КАК КодРегиона
|ИЗ
| Справочник.Регионы КАК Регионы";
ТаблицаРегионов = Запрос.Выполнить().Выгрузить();
// 2. Преобразуем ТаблицуЗначений в Массив Структур с очисткой от ссылок
МассивСтрок = Новый Массив;
Для Каждого СтрокаТЗ Из ТаблицаРегионов Цикл
СтрокаСтруктура = Новый Структура;
СтрокаСтруктура.Вставить("Код", СтрокаТЗ.Код);
СтрокаСтруктура.Вставить("Наименование", СтрокаТЗ.Наименование);
СтрокаСтруктура.Вставить("КодРегиона", СтрокаТЗ.КодРегиона);
// Вместо ссылки передаем ее строковый уникальный идентификатор (GUID)
СтрокаСтруктура.Вставить("UID", Строка(СтрокаТЗ.Ссылка.УникальныйИдентификатор()));
МассивСтрок.Добавить(СтрокаСтруктура);
КонецЦикла;
// 3. Формируем итоговую структуру для отправки
СтруктураПакет = Новый Структура;
СтруктураПакет.Vоставить("ИмяТаблицы", "Регионы");
СтруктураПакет.Вставить("ДатаФормирования", ТекущаяДата());
СтруктураПакет.Вставить("ДанныеТаблицы", МассивСтрок);
// 4. Сериализуем структуру в JSON-строку
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СтруктураПакет);
СтрокаJSON = ЗаписьJSON.Закрыть();
// 5. Подключаемся к Web-сервису и передаем строку
// Параметр метода "ПринятьДанные" на Web-сервисе должен иметь тип "string" (xs:string)
Попытка
Определение = Новый WSОпределения("http://localhost/BaseReceiver/ws/obmen.1cws?wsdl", "Admin", "123");
Прокси = Новый WSProxy(Определение, "WebServiceNamespace", "Obmen", "ObmenSoap");
Прокси.Пользователь = "Admin";
Прокси.Пароль = "123";
Результат = Прокси.ПринятьДанные(СтрокаJSON);
Возврат Результат;
Исключение
ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Ошибка при передаче данных через Web-сервис!";
КонецПопытки;
КонецФункции
Теперь проанализируем, как должна выглядеть функция Web-сервиса на стороне принимающей базы. Web-сервер принимает строку, парсит ее обратно в структуру 1С и обрабатывает полученные данные (ищет элементы по GUID или создает новые).
// Функция Web-сервиса, опубликованная в базе-приемнике
// Входной параметр: ДанныеСтрокой (Тип XDTO: string)
Функция ПринятьДанные(ДанныеСтрокой) Экспорт
// 1. Десериализуем строку JSON обратно в объекты 1С
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ДанныеСтрокой);
Попытка
ПолученнаяСтруктура = ПрочитатьJSON(ЧтениеJSON);
Исключение
ЧтениеJSON.Закрыть();
Возврат "Ошибка парсинга JSON: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
ЧтениеJSON.Закрыть();
// 2. Воссоздаем локальную ТаблицуЗначений для удобства обработки
ТаблицаРегионов = Новый ТаблицаЗначений;
ТаблицаРегионов.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка"));
ТаблицаРегионов.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТаблицаРегионов.Колонки.Добавить("КодРегиона", Новый ОписаниеТипов("Число"));
ТаблицаРегионов.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("СправочникСсылка.Регионы"));
МассивДанных = ПолученнаяСтруктура.ДанныеТаблицы;
НачатьТранзакцию();
Попытка
Для Каждого ЭлементИзМассива Из МассивДанных Цикл
// Пытаемся найти существующий элемент по UID или по Коду
УИД = Новый УникальныйИдентификатор(ЭлементИзМассива.UID);
НайденнаяСсылка = Справочники.Регионы.ПолучитьСсылку(УИД);
// Проверяем, существует ли реальный объект в базе данных
Если ОбщегоНазначения.СсылкаСуществует(НайденнаяСсылка) Тогда
ОбъектРегион = НайденнаяСсылка.ПолучитьОбъект();
Иначе
// Если не найден по UID, пробуем найти по бизнес-коду
СсылкаПоКоду = Справочники.Регионы.НайтиПоКоду(ЭлементИзМассива.Код);
Если ЗначениеЗаполнено(СсылкаПоКоду) Тогда
ОбъектРегион = СсылкаПоКоду.ПолучитьОбъект();
Иначе
// Если объект совсем не найден, создаем новый с переданным UID
ОбъектРегион = Справочники.Регионы.СоздатьЭлемент();
ОбъектРегион.УстановитьСсылкуНового(НайденнаяСсылка);
КонецЕсли;
КонецЕсли;
// Заполняем реквизиты данными
ОбъектРегион.Код = ЭлементИзМассива.Код;
ОбъектРегион.Наименование = ЭлементИзМассива.Наименование;
ОбъектРегион.КодРегиона = ЭлементИзМассива.КодРегиона;
ОбъектРегион.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат "Ошибка при записи данных в базу-приемник: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Возврат "Успешно обработано строк: " + МассивДанных.Количество();
КонецФункции
Существует еще один надежный способ передачи произвольных данных. Он подходит в тех случаях, когда конфигурации баз идентичны и вы уверены, что типы метаданных совпадают. Этот метод базируется на помещении структуры или таблицы в системный объект ХранилищеЗначения с последующим кодированием в строку Base64.
Проанализируем пример кода упаковки данных на стороне отправителя:
// Упаковываем сложную структуру в Base64-строку
Хранилище = Новый ХранилищеЗначения(СтруктураПакет, Новый СжатиеДанных(9));
ДвоичныеДанные = Хранилище.Получить();
СтрокаBase64 = ПолучитьСтрокуBase64ИзДвоичныхДанных(ДвоичныеДанные);
// Передаем полученную СтрокуBase64 через параметр Web-сервиса
Результат = Прокси.ПринятьДанныеBase64(СтрокаBase64);
Разберем обратную процедуру распаковки на стороне получателя:
// Принимаем Base64-строку и восстанавливаем исходный объект
ДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтрокиBase64(СтрокаBase64);
Хранилище = Новый ХранилищеЗначения(ДвоичныеДанные);
ВосстановленнаяСтруктура = Хранилище.Получить(); // Получили исходную структуру
Важное предостережение: Метод с ХранилищеЗначения привлекателен тем, что вам не нужно вручную преобразовывать таблицы значений в массивы структур. Платформа 1С берет это на себя при сжатии данных. Однако помните, что если в таблице значений содержатся ссылки на элементы справочников, которых нет в базе-приемнике, то после распаковки вы получите битые ссылки. Поэтому даже при данном подходе рекомендуется предварительно очищать передаваемую таблицу от прямых ссылок, заменяя их строковыми реквизитами.
В современной разработке на платформе 1С:Предприятие для интеграции баз все чаще используются HTTP-сервисы вместо классических Web-сервисов (SOAP). Выделим ключевые преимущества такого перехода:
Подводя итог, отметим, что самым надежным, масштабируемым и современным способом передачи сложных структур и таблиц данных через веб-службы является их предварительная обработка (избавление от прямых ссылочных типов) и последующая сериализация в формат JSON с передачей в виде обычной строки. Для быстрого запуска подобного обмена можно использовать готовую заготовку интеграции через HTTP и JSON.