Почему при чтении XML через XDTO список из одного элемента считывается как объект и как это исправить

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

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

Выясним причину поведения платформы

Разберем по шагам, почему фабрика XDTO ведет себя подобным образом. Все зависит от того, используется ли строго заданная схема XML (XSD) и как именно происходит чтение файла:

  1. Отсутствие жесткой привязки к XSD или динамический тип: Когда фабрика XDTO выполняет чтение XML-документа без жестко заданного типа или когда узел определяется динамически (например, anyType), платформенный парсер опирается исключительно на фактически встречающиеся теги. Встретив ровно один тег с конкретным именем, парсер интерпретирует его как единственный объект. Если же в файле идут два или более одинаковых тега подряд, платформа определяет их как коллекцию и оборачивает в СписокXDTO.
  2. Особенность работы с корневыми тегами: Согласно спецификации XML, файл может иметь только один корневой элемент. При использовании метода ФабрикаXDTO.ПрочитатьXML() непосредственно для чтения корневого узла результат всегда будет считаться одиночным объектом. Коллекция может существовать только в качестве вложенных элементов родительского объекта.

Такое поведение проявляется не только при разборе XML-файлов, но и при вызове SOAP Web-сервисов, а также при преобразовании данных в формат JSON.

Способ 1. Использование встроенного метода ПолучитьСписок()

Если структура данных описана в схеме XSD и для свойства задано значение верхней границы maxOccurs больше 1 (или unbounded / -1), у платформы 1С есть штатный механизм для гарантированного получения списка.

Вместо прямого обращения к свойству через точку (например, РодительскийОбъект.ИмяСвойства) следует использовать метод ПолучитьСписок(). Рассмотрим пример кода:


// Гарантированно получаем СписокXDTO, независимо от количества элементов (0, 1 или более)
КоллекцияДокументов = РодительскийОбъект.ПолучитьСписок("docum");

Для Каждого ЭлементСписка Из КоллекцияДокументов Цикл
    // Обработка элемента
КонецЦикла;

Важное замечание: Метод ПолучитьСписок() вернет объект СписокXDTO даже в том случае, если в XML-файле присутствовал всего один элемент или тег вовсе отсутствовал. Исключение возникнет только тогда, когда в самой XSD-схеме данное свойство объявлено как одиночное (то есть maxOccurs = 1).

Способ 2. Универсальная функция-обертка для приведенных данных

На практике часто встречаются ситуации, когда схема XSD недоступна, тип является динамическим или происходит работа с произвольными данными XML. В этом случае прямая передача результата в цикл Для Каждого ... Из ... Цикл вызовет ошибку времени выполнения, если результатом оказался одиночный ОбъектXDTO.

Проанализируем универсальное решение, которое позволяет привести полученный объект или список к стандартному массиву 1С. Поскольку платформа 1С вычисляет выражение после ключевого слова Из в цикле Для Каждого только один раз, мы можем написать легкую функцию-преобразователь:


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

Посмотрим, как удобно использовать такую функцию в основном коде программы при обходе элементов:


// Чтение XML через фабрику
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл("status_out_single.xml");

ДанныеОтвета = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
ЧтениеXML.Закрыть();

// Применяем функцию для безопасного обхода
Для Каждого ЭлементDocum Из ПолучитьДанныеСпискаXDTO(ДанныеОтвета.docum) Цикл
    // Код обработки каждого документа
    Сообщить(ЭлементDocum.Status);
КонецЦикла;

Способ 3. Итерационное чтение с помощью функции-счетчика

Другой подход заключается в написании вспомогательной функции для последовательного считывания элементов. Это полезно, когда необходимо обрабатывать элементы в цикле Пока без предварительного создания массива в памяти:


Функция ПрочитатьСледующийЭлементXDTO(Источник, ЗначениеЭлемента, Счетчик)
    
    Если Источник = Неопределено Тогда
        Возврат Ложь;
    КонецЕсли;
    
    Если ТипЗнч(Источник) = Тип("СписокXDTO") Тогда
        Если Счетчик < Источник.Количество() Тогда
            ЗначениеЭлемента = Источник.Получить(Счетчик);
            Счетчик = Счетчик + 1;
            Возврат Истина;
        Иначе
            Возврат Ложь;
        КонецЕсли;
    ИначеЕсли ТипЗнч(Источник) = Тип("ОбъектXDTO") Тогда
        Если Счетчик = 0 Тогда
            ЗначениеЭлемента = Источник;
            Счетчик = Счетчик + 1;
            Возврат Истина;
        Иначе
            Возврат Ложь;
        КонецЕсли;
    КонецЕсли;
    
    Возврат Ложь;
    
КонецФункции

Использование функции в практическом коде выглядит следующим образом:


ТекущийЭлемент = Неопределено;
СчетчикЭлементов = 0;

Пока ПрочитатьСледующийЭлементXDTO(ДанныеОтвета.docum, ТекущийЭлемент, СчетчикЭлементов) Цикл
    // Выполняем действия с ТекущийЭлемент
    Сообщить(ТекущийЭлемент.Код);
КонецЦикла;

Резюме

Странное на первый взгляд поведение фабрики XDTO при чтении одиночных элементов является особенностью реализации платформы 1С. Для написания надежного и отказоустойчивого кода рекомендуется:

  1. При наличии строгой XSD-схемы использовать метод ОбъектXDTO.ПолучитьСписок("ИмяСвойства").
  2. При работе с динамическими типами или произвольным XML применять функцию-обертку, приводящую одиночный ОбъектXDTO к массиву.
← На главную