Как исправить ошибку «Несоответствие типов XDTO: Свойство является списковым» при вызове веб-сервиса?

Программист 1С v8.3 (Обычные формы) Торговля и дистрибуция
← На главную

При интеграции платформы 1С с внешними SOAP веб-сервисами разработчики нередко сталкиваются с ситуацией, когда инициализация прокси проходит успешно, но при вызове конкретной операции платформа возвращает исключение:

«При вызове веб-сервиса произошла ошибка. Неизвестная ошибка. Несоответствие типов XDTO: Свойство является списковым».

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

Выясним причину ошибки XDTO

Проанализируем архитектуру работы платформы 1С с протоколом SOAP. Платформа строго придерживается стандарта WS-I Basic Profile 1.1. Согласно этому профилю, допустимым стилем описания веб-сервисов является Document/Literal (или Document/Wrapped). Использование устаревшего стиля RPC/Encoded платформой официально не поддерживается.

В веб-сервисах со стилем кодирования RPC/Encoded массивы передаются через специальные конструкции наподобие SOAP-ENC:arrayType или soapenc:Array. Встроенная фабрика XDTO 1С при десериализации полученного XML-пакета не может сопоставить эту структуру с жестко типизированной объектной моделью. В результате валидатор ожидает скалярный тип данных, но натыкается на массив элементов, что приводит к фатальному сбою десериализации.

Другая распространенная причина кроется в самом описании схемы XSD внутри WSDL. Если в схеме тип поля оформлен через конструктор <xsd:all> вместо <xsd:sequence>, в нем по спецификации W3C запрещено использование свойств со списковыми значениями (maxOccurs > 1). Если же сервер возвращает несколько одноименных элементов, платформа 1С считает это грубым нарушением схемы.

Решение 1. Переключение адреса WSDL на стиль Document/Literal

Рассмотрим самый быстрый и правильный способ решения проблемы, не требующий написания обходных путей. Многие поставщики API (включая дистрибьютора Merlion) поддерживают параллельные точки входа для разных стандартов передачи данных. Обратите внимание на путь в URL веб-сервиса:

  1. Маркер /re/ в адресе означает RPC/Encoded — данный формат вызывает ошибку в 1С;
  2. Маркер /rl/ означает RPC/Literal;
  3. Маркер /dl/ означает Document/Literal — полностью совместимый с 1С стандарт.

Заменим адрес сервиса в коде конфигурации на конечную точку /dl/. Посмотрим на пример рабочего кода подключения:


Пользователь = "Логин";
Пароль = "Пароль";

// Используем адрес с маркером dl вместо re
АдресWSDL = "https://api.merlion.com/dl/mlservice3?wsdl";
URIПространстваИмен = "https://api.merlion.com/dl/mlservice3";

Определения = Новый WSОпределения(АдресWSDL, Пользователь, Пароль);
ВебСервис = Новый WSПрокси(Определения, URIПространстваИмен, "MLService", "MLPort");
ВебСервис.Пользователь = Пользователь;
ВебСервис.Пароль = Пароль;

Результат = ВебСервис.getCatalog("All");

При таком подходе фабрика XDTO корректно считывает типы данных, и результат возвращается в виде стандартных объектов ОбъектXDTO или коллекций СписокXDTO, которые можно обходить стандартным циклом.

Решение 2. Локальная правка WSDL-схемы

Если поставщик сервиса не предоставляет конечную точку в формате Document/Literal, разберем по шагам способ с переопределением описания сервиса на стороне клиента:

  1. Сохраняем WSDL-файл и сопутствующие ему XSD-схемы из браузера на локальный диск сервера 1С или в каталог информационной базы.
  2. Открываем файл схемы в текстовом редакторе и находим определение типа, на котором возникает ошибка.
  3. Заменяем блок <xsd:all> на блок <xsd:sequence>.
  4. Для элементов массива явно прописываем атрибуты minOccurs="0" и maxOccurs="unbounded".
  5. При создании объекта WSОпределения указываем путь к отредактированному локальному файлу, а в объекте WSПрокси оставляем сетевой адрес реального веб-сервиса.

ПутьКЛокальномуWSDL = "C:\WS\mlservice3_fixed.wsdl";
URIПространстваИмен = "https://api.merlion.com/re/mlservice3";

Определения = Новый WSОпределения(ПутьКЛокальномуWSDL, Пользователь, Пароль);
ВебСервис = Новый WSПрокси(Определения, URIПространстваИмен, "MLService", "MLPort");
ВебСервис.Пользователь = Пользователь;
ВебСервис.Пароль = Пароль;

Результат = ВебСервис.getCatalog("All");

Решение 3. Отправка прямого SOAP-запроса через HTTPСоединение

Механизм WSПрокси выполняет строгую проверку входящих и исходящих XML-пакетов, которую невозможно отключить программно. Если WSDL содержит несовместимые конструкции, наиболее надежным способом на чистой платформе 1С является формирование низкоуровневого HTTP-запроса. Проанализируем реализацию такого вызова:


Сервер = "api.merlion.com";
Ресурс = "/re/mlservice3";

Соединение = Новый HTTPСоединение(Сервер, 443, Пользователь, Пароль, , , Новый ЗащищенноеСоединениеOpenSSL());

ТелоSOAP = 
"<soapenv:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" 
|                  xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" 
|                  xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" 
|                  xmlns:mer=""https://api.merlion.com/re/mlservice3"">
|   <soapenv:Header/>
|   <soapenv:Body>
|      <mer:getCatalog>
|         <cat_id xsi:type=""xsd:string"">All</cat_id>
|      </mer:getCatalog>
|   </soapenv:Body>
|</soapenv:Envelope>";

Запрос = Новый HTTPЗапрос(Ресурс);
Запрос.Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");
Запрос.Заголовки.Вставить("SOAPAction", "https://api.merlion.com/re/mlservice3#getCatalog");
Запрос.УстановитьТелоИзСтроки(ТелоSOAP, КодировкаТекста.UTF8);

Ответ = Соединение.ОтправитьДляОбработки(Запрос);

Если Ответ.КодСостояния = 200 Тогда
    СтрокаОтветаXML = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
    Чтение = Новый ЧтениеXML;
    Чтение.УстановитьСтроку(СтрокаОтветаXML);
    DOM = Новый СоздательDOM;
    ДокументDOM = DOM.СоздатьДокумент(Чтение);
    // Разбираем узлы DOM без участия жестких схем XDTO
КонецЕсли;

Решение 4. Использование внешней сборки .NET через COM-обертку

Если требуется выгружать большие объемы данных с высокой скоростью десериализации, рассмотрим вариант применения WCF-клиента на платформе .NET, подключаемого к 1С через интерфейс COMОбъект.

Посмотрим на пример инициализации и обработки каталога в 1С с использованием классов .NET:


Врап = Новый COMОбъект("NetObjectToIDispatch45");
ФайлСборки = "C:\APILib\MerlionApi.dll";
MLPortClient = Врап.ПолучитьТипИзСборки("MerlionApi.com.merlion.api.MLService", ФайлСборки);

Клиент = Врап.СоздатьОбъект(MLPortClient);
СетевыеРеквизиты = Врап.СоздатьОбъект("System.Net.NetworkCredential", Логин, Пароль);
URLСервиса = "https://api.merlion.com/dl/mlservice3";
URIОбъект = Врап.СоздатьОбъект("System.Uri", URLСервиса);

Клиент.Credentials = СетевыеРеквизиты.GetCredential(URIОбъект, "Basic");
Клиент.PreAuthenticate = Истина;

Товары = Клиент.getCatalog("N1");

Для Каждого Стр Из Товары Цикл
    Сообщить("ИД: " + Стр.ID + ", Наименование: " + Стр.Description);
КонецЦикла;

Работа со списковыми полями и обработка ограничений по частоте (Rate Limit)

При передаче исходящих параметров в списковые свойства объектов XDTO помните важное правило: прямое присвоение значения переменной вызовет ошибку «Свойство является списковым». Значения следует добавлять исключительно в коллекцию:


// Ошибочно: ОбъектXDTO.Items = Элемент;
// Правильно:
ОбъектXDTO.Items.Добавить(Элемент);

Также при регулярной работе с API поставщиков учитывайте лимиты обращений к серверу (обычно действует ограничение до 1–2 запросов в секунду). При превышении лимита сервис вернет ошибку. Чтобы исключить аварийное завершение регламентного задания или обработки, обернем вызов в блок Попытка ... Исключение с механизмом паузы и повтора:


Успешно = Ложь;
Попыток = 0;

Пока Не Успешно И Попыток < 5 Цикл
    Попытка
        Попыток = Попыток + 1;
        ДанныеЗаказа = ВебСервис.getOrderLines(НомерЗаказа, "0");
        Успешно = Истина;
    Исключение
        // Делаем паузу перед повторным запросом
        Приостановить(2);
    КонецПопытки;
КонецЦикла;
← На главную