К сожалению, возможности загрузки какой-нибудь schema для json файликов нам не подвезли. Зато подвезли для xml. XDTO пакеты, официальная документация тут, отличная статья по пакетам тут. И по счастливому стечению обстоятельств, платформа дает нам возможность прочитать json файл в xdto пакет, как бы это безумно ни звучало (напомню, что буква x в названии xdto означает xml). А раз мы можем прочитать json в xdto, значит, мы можем прочитать его в "определенный" xdto, в котором мы заранее установили все необходимые настройки - возможность наличия пустых/необязательных полей, количество элементов в поле и прочее.
Итак, возьмем какой-нибудь файлик в формате json:
{
"Date": "2023-01-01T00:00:00",
"Number": 123,
"Name": "Некоторое имя"
}
И попробуем преобразовать его в XDTO объект:
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Строка); // Строка - наш JSON в виде строки
Данные = ФабрикаXDTO.ПрочитатьJSON(Чтение);
Чтение.Закрыть();
Если открыть объект, можно увидеть следующую картину:
Число преобразовалось в число, строка в строку, а дата... Дата тоже преобразовалась в строку, потому что по умолчанию десериализатор вообще не в курсе, что мы там к чему хотим преобразовать. Чтобы подсказать, что же мы ожидаем в каждом поле, надо создать XDTO-пакет.
Для этого идем в конфигуратор, создаем новый пакет и задаем ему URI пространства имен. Вообще в этом поле может быть абсолютно любое строковое значение, но есть чисто нормативное правило задавать его в форме URL (в примере я буду использовать "http://www.is_test.kz"). В новом пакете создаем тип объекта (назовем его "ISTest") и добавляем свойства, описанные в JSON. Должно получиться как-то так
Для каждого свойства установим тип. Для этого нужно выбрать свойство, и справа на палитре свойств (привет тавтология) открываем список доступных типов. Нам нужен пакет "http://www.w3.org/2001/XMLSchema" который находится в самом низу. Для поля date в пакете находим тип dateTime, для поля number ищем тип int, и для поля name устанавливаем тип string. Теперь нам нужно подправить код чтения JSON, передав ему наш новый XDTO-пакет, в который мы хотим этот самый JSON прочитать
Тип = ФабрикаXDTO.Тип("http://www.is_test.kz", "ISTest");
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Строка);
Данные = ФабрикаXDTO.ПрочитатьJSON(Чтение, Тип);
Чтение.Закрыть();
На выходе в переменной "Данные" мы увидим это
Отлично, дата теперь преобразовывается в дату. И хорошо, если дата приходит нам в таком формате "2023-01-01T00:00:00". Но ведь системы бывают разные, интеграции бывают разные, и дату вам могут отправить в совершенно любом варианте. Давайте поменяем наш JSON файл:
{
"Date": "10.09.1993 15:00:00",
"Number": 123,
"Name": "Некоторое имя"
}
При выполнении преобразование в наш XDTO объект мы отхватим ошибку:
"Ошибка при вызове метода контекста (ПрочитатьJSON): Несоответствие типов XDTO: Ошибка проверки данных XDTO: Значение: '10.09.1993 15:00:00' не соответствует простому типу: {http://www.w3.org/2001/XMLSchema}dateTime"
Это значит, что если мы должны работать с этим полем, как с датой, но сторонняя система может отправлять дату в "своем личном независимом" формате, мы должны обучить нашу функцию приводить это значение к нужному типу. Помощником в преобразовании (в терминах платформы и далее по тексту - восстановление) является третий, четвертый, пятый, шестой и седьмой параметры метода "ПрочитатьJSON" с названиями "ИмяФункцииВосстановления", "МодульФункцииВосстановления", "ДополнительныеПараметры, "ТипыДляОбработкиВосстановления" и "ИменаСвойствДляВосстановления" соответственно. Все они необязательные.
ИмяФункцииВосстановления - строка - имя функции, которую мы будем вызывать. На вход функция должна принимать четыре параметра - "Свойство", "Тип", "Значение", ДополнительныеПараметры", возвращать функция должна объект, допустимый для XDTO сериализации.
МодульФункцииВосстановления - нужно передать модуль, в котором будет описана наша функция восстановления. В роли модуля может выступать общий неглобальный модуль, форма клиентского приложения или команда командного интерфейса.
ДополнительныеПараметры - произвольный - любое значение для передачи в функцию восстановления
ТипыДляОбработкиВосстановления - массив, в массиве передаем типы объектов XDTO, которые нужно обработать через функцию восстановления
ИменаСвойствДляВосстановления - массив, строковые значения (я так и не смог передать сюда массив с именами полей, почему то происходит аварийная ошибка и закрывается режим предприятия)
А еще нам нужно создать свой тип, который на прием может получить строку, а в процессе восстановления приведет это значение к типу "дата". Вернемся к нашему XDTO-пакету и создадим там тип значения. В типе значений добавим новый тип и назовем его "dateTime". В новосозданном типе найдем свойство "Типы объединения" и укажем там 2 значения, dateTime и string из пакета "http://www.w3.org/2001/XMLSchema"
И теперь в нашем объекте "ISTest" в поле "Date" выберем тип dateTime из нашего пакета "http://www.is_test.kz"
Ну и осталось написать код для восстановления. Для начала нам надо показать, какие типы мы будем восстанавливать
ТипыВосстановления = Новый Массив();
ТипыВосстановления.Добавить(ФабрикаXDTO.Тип("http://www.is_test.kz", "dateTime"));
И передать нужные параметры в метод
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(JSON);
Данные = ФабрикаXDTO.ПрочитатьJSON(
Чтение,
ТипФабрики,
"ВосстановлениеСвойств", // имя функции восстановления
ВалидацияJSON, // модуль, в котором находится функция
, // тут могут быть дополнительные параметры, которые полетят в функцию восстановления
ТипыВосстановления // массив типов XDTO, которые необходимо восстановить
);
Чтение.Закрыть();
И напишем саму функцию восстановления
Функция ВосстановлениеСвойств(Свойство, Тип, Значение, ДополнительныеПараметры) Экспорт
Фабрика = Значение.Фабрика();
Если НРег(Свойство) = "date" Тогда
Попытка
Дата = Дата(Значение.Значение);
Исключение
ТекстОшибки = НСтр("ru = 'В поле %1 должно быть значение с типом Дата в формате дд.ММ.гггг ЧЧ:мм:сс'");
ВызватьИсключение СтрШаблон(ТекстОшибки, Свойство);
КонецПопытки;
Возврат Фабрика.Создать(Тип, Дата);
КонецЕсли;
КонецФункции
В функции сначала мы получаем фабрику XDTO для создания нужного значения. Потом мы проверяем, а то ли поле мы сейчас собираемся восстанавливать (В параметр "Свойство" прилетает строка с именем поля). Потом идет само восстановление. Так как мы знаем, в каком формате нам должна прилететь дата, мы можем написать нужный код для восстановления. Если же в процессе вылетит исключение, обрабатываем его и отправляем вверх по стеку. В итоге мы создаем в фабрике значение с нужным типом (тип поля прилетает в параметр "Тип"), и устанавливаем полю наше восстановленное значение.
Проверяем:
Поле мы получили в формате даты. Отлично. Усложним задачу.
При интеграции мы хотим проверять, что поле "Date" должно быть заполнено обязательно, поле "Number" может быть пустым, а поле "Name" может быть во входящем файле, а может и не быть, но если поле в файле есть, оно должно быть заполнено. Для этого нам надо вернуться в наш XDTO-пакет.
Открывая палитру свойств полей нужно установить следующее:
Поле "Date" - свойство "Возможно пустое" - ложь, свойство "Минимальное количество" - 1
Поле "Number" - свойство "Возможно пустое" - истина, свойство "Минимальное количество" - 1
Поле "Name" - свойство "Возможно пустое" - ложь, свойство "Минимальное количество" - 0
Теперь если в поле "Number" вы захотите передать 0, или пустую строку, XDTO преобразует это как 0, если передать null, XDTO преобразует это как Неопределено. Но если в файле не будет поля "Number", то XDTO выдаст не очень понятную ошибку, но в ней будут такие строки:
Структура объекта не соответствует типу: {http://www.is_test.kz}ISTest
Не установлено значение одного из следующих свойств: Number
Далее, если из файла JSON мы удалим поле "Name", то получим вот такие данные:
То есть в самом XDTO объекте поле останется, но ему будет присвоено значение Неопределено.
Но, если мы передадим пустую строку:
То никакой ошибки не будет. Объект спокойно проглотит пустую строку, хотя мы устанавливали в свойствах, что если поле "Name" есть, оно должно быть заполнено. Это особенности работы с этим странным механизмом. Но мы умеем внедряться в восстановление полей, а значит можем вручную обработать пустую строку.
Добавляем новый тип для восстановления
ТипыВосстановления = Новый Массив();
ТипыВосстановления.Добавить(ФабрикаXDTO.Тип("http://www.is_test.kz", "dateTime"));
ТипыВосстановления.Добавить(ФабрикаXDTO.Тип("http://www.w3.org/2001/XMLSchema", "string"));
И добавляем кода в метод "ВосстановлениеСвойств"
Функция ВосстановлениеСвойств(Свойство, Тип, Значение, ДополнительныеПараметры) Экспорт
Фабрика = Значение.Фабрика();
Если НРег(Свойство) = "date" Тогда
Попытка
Дата = Дата(Значение.Значение);
Исключение
ТекстОшибки = НСтр("ru = 'В поле %1 должно быть значение с типом Дата в формате дд.ММ.гггг ЧЧ:мм:сс'");
ВызватьИсключение СтрШаблон(ТекстОшибки, Свойство);
КонецПопытки;
Возврат Фабрика.Создать(Тип, Дата);
ИначеЕсли НРег(Свойство) = "name" Тогда
Если Не ЗначениеЗаполнено(Значение.Значение) Тогда
ТекстОшибки = НСтр("ru = 'Поле %1 должно быть заполнено'");
ВызватьИсключение СтрШаблон(ТекстОшибки, Свойство);
КонецЕсли;
Возврат Фабрика.Создать(Тип, Значение.Значение);
КонецЕсли;
КонецФункции
Теперь при обработке файла:
{
"Date": "10.09.1993 15:00:00",
"Number": 123,
"Name": ""
}
Мы получим ошибку:
Поле Name должно быть заполнено
Ну вроде все, мы разобрались с примитивными типами. Но! В JSON есть еще такое понятие как массив.Обработать мы его можем, но с кучей ограничений. Мы не можем просто взять и передать поле в XDTO-пакет
{"nums": [1, 2, 3]}
Оно просто не обработается. Но мы можем передать массив объектов
{"Objects": [
{
"Date": "10.09.1993 15:00:00",
"Number": 1,
"Name": "Первый",
},
{
"Date": "10.09.1993 15:00:00",
"Number": 2,
"Name": "Второй",
},
{
"Date": "10.09.1993 15:00:00",
"Number": 3,
"Name": "Третий",
}
]}
Заведем новое свойство для нашего объекта "ISTest", назовем его "Objects", базовый тип устанавливать ему не будем. В палитре свойств установим полю "Максимальное количество" значение 5. Максимальное количество в этом случае будет определять максимальную длину массива.
Получится примерно так:
Ну и, конечно, для него работают все наши предыдущие правила. Например, такой файл обработается нормально:
{"Objects": [
{
"Date": "10.09.1993 15:00:00",
"Number": null,
"Name": "Первый",
},
{
"Date": "10.09.1993 15:00:00",
"Number": 0
},
{
"Date": "10.09.1993 15:00:00",
"Number": 3,
"Name": "Третий",
}
]}
Все, что остается сделать, это закидывать входящие файлы в XDTO-пакет, если вылетит исключение, файл невалиден и работать с ним смысла нет, если же файл обработается нормально, можно принимать файл и отправлять в дальнейшую обработку.
Вот такие возможности нам предлагает валидация через XDTO пакеты. Никого не агитирую валидировать JSON именно так, только вам решать как обрабатывать входящие данные. Но знать такой механизм, как XDTO-пакеты, нужно.
Полный код итогового модуля:
Функция ВалидацияJSONчерезXDTO(JSON) Экспорт
ТипФабрики = ФабрикаXDTO.Тип("http://www.is_test.kz", "ISTest");
ТипыВосстановления = Новый Массив();
ТипыВосстановления.Добавить(ФабрикаXDTO.Тип("http://www.is_test.kz", "dateTime"));
ТипыВосстановления.Добавить(ФабрикаXDTO.Тип("http://www.w3.org/2001/XMLSchema", "string"));
Попытка
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(JSON);
Данные = ФабрикаXDTO.ПрочитатьJSON(
Чтение,
ТипФабрики,
"ВосстановлениеСвойств",
ВалидацияJSON,
,
ТипыВосстановления
);
Чтение.Закрыть();
Исключение
ТекстОшибки = НСтр("ru = 'При обработке входящего файла произошла ошибка: %1%2'");
Ошибка = ОписаниеОшибки();
ПолныйТекстОшибки = СтрШаблон(ТекстОшибки, Символы.ПС, Ошибка);
ВызватьИсключение ПолныйТекстОшибки;
КонецПопытки;
КонецФункции
Функция ВосстановлениеСвойств(Свойство, Тип, Значение, ДополнительныеПараметры) Экспорт
Фабрика = Значение.Фабрика();
Если НРег(Свойство) = "date" Тогда
Попытка
Дата = Дата(Значение.Значение);
Исключение
ТекстОшибки = НСтр("ru = 'В поле %1 должно быть значение с типом Дата в формате дд.ММ.гггг ЧЧ:мм:сс'");
ВызватьИсключение СтрШаблон(ТекстОшибки, Свойство);
КонецПопытки;
Возврат Фабрика.Создать(Тип, Дата);
ИначеЕсли НРег(Свойство) = "name" Тогда
Если Не ЗначениеЗаполнено(Значение.Значение) Тогда
ТекстОшибки = НСтр("ru = 'Поле %1 должно быть заполнено'");
ВызватьИсключение СтрШаблон(ТекстОшибки, Свойство);
КонецЕсли;
Возврат Фабрика.Создать(Тип, Значение.Значение);
КонецЕсли;
КонецФункции
XDTO-пакет (сохранить в файл с расширение xsd и импортировать в пакет в конфигураторе):
<xs:schema xmlns:tns="http://www.is_test.kz" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.is_test.kz" attributeFormDefault="unqualified" elementFormDefault="qualified">
<xs:simpleType name="dateTime">
<xs:union memberTypes="xs:string xs:dateTime"/>
</xs:simpleType>
<xs:complexType name="ISTest">
<xs:sequence>
<xs:element name="Objects" maxOccurs="5">
<xs:complexType>
<xs:sequence>
<xs:element name="Date" type="tns:dateTime"/>
<xs:element name="Number" type="xs:int" nillable="true"/>
<xs:element name="Name" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>