При работе с интеграциями через веб-сервисы или при обмене данными в формате XML программисты 1С часто сталкиваются с тем, что платформа автоматически добавляет атрибут xsi:type в генерируемые узлы. Это происходит потому, что механизм XDTO (XML Data Transfer Objects) пытается явно указать тип данных для обеспечения однозначности при обратном чтении. Однако многие сторонние веб-сервисы настроены на строгую проверку структуры и выдают ошибку, если видят в теге «лишние» атрибуты пространства имен http://www.w3.org/2001/XMLSchema-instance.
В этой статье мы подробно разберем причины появления этой особенности и рассмотрим несколько способов ее обхода — от простых манипуляций со строками до глубокой настройки работы с фабрикой XDTO.
Для начала проанализируем ситуацию, почему 1С считает нужным добавлять этот атрибут. Основная причина заключается в неоднозначности определения типа в XSD-схеме. Если свойство родительского объекта определено как anyType или имеет абстрактный тип, платформа обязана указать в XML, какой именно конкретный тип данных используется в данный момент. Также xsi:type часто появляется, если вы создаете объект напрямую через ФабрикаXDTO.Создать(), не привязываясь к свойству конкретного родителя.
Разберем способы решения этой задачи по возрастанию их сложности и «чистоты» реализации.
Рассмотрим наиболее правильный с точки зрения архитектуры XDTO подход. Вместо того чтобы создавать объект «в воздухе», передавая в фабрику только тип, мы можем создать его на основе определения свойства того объекта, в который он будет вложен. В этом случае контекст типа уже задан родителем, и 1С может опустить явное указание типа.
Посмотрим на пример кода:
// Вместо этого:
// ТипОбъекта = ФабрикаXDTO.Тип("http://service", "TYPE");
// НовыйОбъект = ФабрикаXDTO.Создать(ТипОбъекта);
// Используем такой подход:
РодительскийТип = ФабрикаXDTO.Тип("http://service", "ParentType");
РодительскийОбъект = ФабрикаXDTO.Создать(РодительскийТип);
// Получаем определение свойства из метаданных XDTO
СвойствоТипа = РодительскийОбъект.Свойства().Получить("TYPE");
НовыйОбъект = ФабрикаXDTO.Создать(СвойствоТипа.Тип);
// Теперь при записи РодительскийОбъект.TYPE = НовыйОбъект;
// атрибут xsi:type с большой вероятностью не добавится.
Важный нюанс: этот способ сработает только в том случае, если в вашей XSD-схеме тип элемента жестко зафиксирован, а не является расширяемым или неопределенным.
Если вы записываете объект XDTO в файл или строку напрямую, можно воспользоваться методом ЗаписатьXML самой фабрики. Мы можем принудительно указать локальное имя тега и пространство имен, что часто заставляет платформу отказаться от добавления атрибута типа.
Проанализируем пример такой записи:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
// Указываем локальное имя и пространство имен вручную при записи
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, ВашОбъектXDTO, "TYPE", "http://service");
Результат = ЗаписьXML.Закрыть();
В данном случае, передавая параметры ЛокальноеИмя и ПространствоИмен, мы переопределяем стандартное поведение сериализатора, и он перестает уточнять тип через xsi:type.
Если XDTO упорно продолжает добавлять ненужные атрибуты, а сервис на стороне получателя крайне чувствителен к формату, мы можем прибегнуть к комбинированному методу. Часть XML мы будем формировать вручную, используя объект ЗаписьXML, а вложенные данные (если их много) записывать через фабрику.
Рассмотрим, как это реализовать:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\temp\data.xml");
ЗаписьXML.ЗаписатьОбъявлениеXML();
// Записываем начало элемента вручную без всяких атрибутов
ЗаписьXML.ЗаписатьНачалоЭлемента("TYPE");
// Записываем внутренние данные
// Если внутренние элементы простые, можно использовать ЗаписатьБезОбработки
ЗаписьXML.ЗаписатьБезОбработки("<OTR>" + ТипОтраслиКод + "</OTR>");
ЗаписьXML.ЗаписатьБезОбработки("<ANI>" + ТипКод + "</ANI>");
// Завершаем элемент
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
Этот метод считается «жестким», так как вы берете на себя контроль за валидностью XML-структуры, однако он дает 100% гарантию отсутствия нежелательных атрибутов.
Если файл не слишком огромен и помещается в оперативную память, наиболее технологичным способом удаления атрибутов будет использование объектной модели документа (DOM). Это надежнее, чем простая текстовая замена, так как исключает риск повреждения данных внутри самих тегов.
Выясним, как это сделать по шагам:
ДокументDOM.Пример программной очистки:
// Выгружаем XDTO в DOM
ЗаписьDOM = Новый ЗаписьDOM;
ДокументDOM = ФабрикаXDTO.ЗаписатьDOM(ВашОбъектXDTO);
// Ищем атрибуты xsi:type
// Пространство имен xsi обычно "http://www.w3.org/2001/XMLSchema-instance"
СписокУзлов = ДокументDOM.ПолучитьЭлементыПоИмени("*");
Для Каждого ТекущийУзел Из СписокУзлов Цикл
Если ТекущийУзел.Атрибуты.Количество() > 0 Тогда
// Пытаемся найти и удалить атрибут type
ТекущийУзел.Атрибуты.УдалитьИмя("type", "http://www.w3.org/2001/XMLSchema-instance");
КонецЕсли;
КонецЦикла;
// Сохраняем результат
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписатьDOM = Новый ЗаписьDOM;
ЗаписатьDOM.Записать(ДокументDOM, ЗаписьXML);
Результат = ЗаписьXML.Закрыть();
Иногда корень проблемы кроется в самой схеме, которую вы импортировали в пакет XDTO. Если у вас есть возможность редактировать схему, обратите внимание на следующие моменты:
elementFormDefault="qualified". Это заставляет 1С более предсказуемо работать с пространствами имен.abstract="true" в определениях типов.xsi:type, чтобы указать, какая именно реализация используется. Чтобы этого избежать, сделайте тип самостоятельным (скопируйте поля базового типа вместо наследования).Мы рассмотрели различные варианты борьбы с избыточной типизацией в XML. Выбор метода зависит от ваших задач:
Помните, что xsi:type — это не ошибка 1С, а строгое следование стандартам XML Schema. Однако в реальном мире программисту часто приходится подстраиваться под специфические требования внешних систем, и приведенные выше инструменты помогут вам в этом.