Валидатор JSON Schema draft-07 для OneScript.
Схема описывает форму данных один раз - и дальше её можно применять к чему угодно: к телу HTTP-запроса, к конфигурационному файлу, к выгрузке из внешней системы. Вместо десятка ручных Если ТипЗнч(...) <> Тип("Строка") получается декларативное описание, которое читается человеком и переиспользуется между сервисами на разных языках.
Пакет не тянет зависимостей: разбор JSON выполняется встроенными ЧтениеJSON и ЗаписьJSON.
opm install jsonschema
#Использовать jsonschema
Схема = JsonSchema.ИзJson("
|{
| ""type"": ""object"",
| ""required"": [""имя"", ""возраст""],
| ""properties"": {
| ""имя"": {""type"": ""string"", ""minLength"": 2},
| ""возраст"": {""type"": ""integer"", ""minimum"": 0}
| },
| ""additionalProperties"": false
|}");
Результат = JsonSchema.ПроверитьJson("{""имя"": ""И"", ""лишнее"": 1}", Схема);
Сообщить(Результат.Корректно); // Нет
Сообщить(Результат.Описание());
// /имя: Длина строки 1 меньше требуемой 2 (minLength)
// (корень): Отсутствует обязательное свойство "возраст" (required)
// /лишнее: Свойство "лишнее" не описано схемой (additionalProperties)Схема задаётся Структурой, Соответствием, значением Истина/Ложь или текстом JSON. Проверяемое значение - любым сочетанием Соответствий, Структур, Массивов и примитивов, то есть в точности тем, что возвращает ПрочитатьJSON.
Каждая ошибка - Структура с четырьмя полями:
Результат = JsonSchema.Проверить(Данные, Схема);
Для Каждого Ошибка Из Результат.Ошибки Цикл
Сообщить(Ошибка.Путь); // /позиции/2/количество - указатель JSON до значения
Сообщить(Ошибка.Ключевое); // minimum - какое ключевое слово не выполнено
Сообщить(Ошибка.Сообщение); // Значение -1 меньше минимума 0
Сообщить(Ошибка.Значение); // -1
КонецЦикла;
// Когда некорректные данные - это авария, а не ветка логики
Результат.ВыброситьЕслиНекорректно();Путь корневого значения - пустая строка. Символы ~ и / в именах свойств экранируются по RFC 6901 (~0 и ~1).
Разбор схемы, компиляция регулярных выражений и разрешение ссылок стоят дороже самой проверки. В цикле схему разбирают заранее:
Валидатор = JsonSchema.Скомпилировать(Схема);
Для Каждого Запись Из Выгрузка Цикл
Если Не Валидатор.Корректно(Запись) Тогда
Отбраковка.Добавить(Валидатор.Проверить(Запись).Описание());
КонецЕсли;
КонецЦикла;Схема = JsonSchema.ИзJson("
|{
| ""definitions"": {
| ""узел"": {
| ""type"": ""object"",
| ""properties"": {""дети"": {""type"": ""array"", ""items"": {""$ref"": ""#/definitions/узел""}}}
| }
| },
| ""$ref"": ""#/definitions/узел""
|}");
JsonSchema.Корректно(Дерево, Схема);Параметры = Новый Структура();
Параметры.Вставить("ПроверятьФормат", Истина); // по умолчанию Ложь
Параметры.Вставить("ОстанавливатьсяНаПервойОшибке", Истина); // по умолчанию Ложь
JsonSchema.Проверить(Данные, Схема, Параметры);ПроверятьФормат включает ключевое слово format, которое спецификация объявляет необязательным. ОстанавливатьсяНаПервойОшибке прекращает обход, как только найдено первое нарушение, - это заметно быстрее, когда нужен только вердикт.
| Метод | Возвращает | Описание |
|---|---|---|
Проверить(Значение, Схема, Параметры = Неопределено) |
JsonSchemaResult |
Проверка со списком ошибок |
Корректно(Значение, Схема, Параметры = Неопределено) |
Булево |
Проверка без подробностей |
Скомпилировать(Схема, Параметры = Неопределено) |
Валидатор |
Разбор схемы для многократного применения |
ПроверитьJson(ТекстJson, Схема, Параметры = Неопределено) |
JsonSchemaResult |
Разбор текста JSON и проверка |
ИзJson(ТекстJson) |
Произвольный |
Разбор текста JSON во встроенные типы |
ПоддерживаемыеФорматы() |
Массив |
Имена проверяемых значений format |
Новый Валидатор(Схема, Параметры = Неопределено), методы Проверить(Значение), Корректно(Значение), ПроверитьJson(ТекстJson).
| Член | Тип | Описание |
|---|---|---|
Корректно |
Булево |
Итог проверки |
Ошибки |
Массив |
Структуры с полями Путь, Ключевое, Сообщение, Значение |
ПерваяОшибка() |
Структура |
Первая ошибка или Неопределено |
Описание() |
Строка |
Человекочитаемый текст, по одной ошибке в строке |
ВыброситьЕслиНекорректно() |
- | Исключение, если значение не прошло проверку |
| Группа | Ключевые слова | Состояние |
|---|---|---|
| Общие | type (включая массив типов), enum, const |
Поддержаны |
| Числа | multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum |
Поддержаны |
| Строки | minLength, maxLength, pattern |
Поддержаны |
| Массивы | items, additionalItems, minItems, maxItems, uniqueItems, contains |
Поддержаны |
| Объекты | properties, required, additionalProperties, patternProperties, minProperties, maxProperties, propertyNames, dependencies |
Поддержаны |
| Комбинаторы | allOf, anyOf, oneOf, not |
Поддержаны |
| Условные | if, then, else |
Поддержаны |
| Логические схемы | true, false как значение схемы |
Поддержаны |
| Ссылки | $ref на указатель внутри документа, definitions |
Поддержаны частично, см. ниже |
| Аннотации | title, description, default, examples, $comment, $schema, $id |
Игнорируются |
| Полуструктурные | format |
Необязательная проверка, по умолчанию выключена |
| Содержимое строк | contentEncoding, contentMediaType |
Игнорируются |
Неизвестные ключевые слова игнорируются, как требует спецификация.
Поддержаны указатели внутри текущего документа: # (корень) и #/путь/к/узлу. Реализован JSON Pointer по RFC 6901: раскодирование ~0, ~1 и последовательностей %XX, проход через элементы массива по индексу, пустые токены. Рекурсивные схемы работают. При наличии $ref соседние ключевые слова узла игнорируются - так требует draft-07.
Не поддержаны: смена базового URI через $id, якорные ссылки вида #имя и загрузка внешних документов. Такая ссылка приводит к исключению с понятным текстом, а не к молчаливо неверному результату.
При включённом ПроверятьФормат проверяются date, time, date-time, email, hostname, ipv4, uri, uri-reference, uuid, json-pointer, regex. Остальные значения format принимаются без проверки - спецификация это разрешает. Не реализованы ipv6, idn-email, idn-hostname, iri, iri-reference, uri-template, relative-json-pointer.
Пакет прогоняет JSON-Schema-Test-Suite для draft-07. Файлы набора лежат в tests/suite/draft7 без изменений, раннер - tests/ОфициальныйНабор_Тесты.os.
| Показатель | Значение |
|---|---|
| Файлов набора | 34 |
| Кейсов всего | 800 |
| Прошло | 764 |
| Исключено | 36 |
| Провалено | 0 |
Исключения перечислены поимённо в функции Исключения() раннера вместе с причиной. Список проверяется на минимальность: если исключённый кейс начнёт проходить, тест сводки об этом сообщит.
| Исключено | Кейсов | Причина |
|---|---|---|
11 групп ref.json |
24 | Смена базового URI через $id не поддерживается |
4 группы ref.json |
8 | Якорные ссылки вида #имя не поддерживаются |
1 группа ref.json |
2 | Загрузка внешних схем не поддерживается |
multipleOf.json, группа float division = inf |
1 | Литерал 1e308 не помещается в тип Число: группа не читается платформой |
const.json, кейс с 9007199254740992.0 |
1 | ПрочитатьJSON округляет дробный литерал до 9007199254740990 |
Три файла набора не включены целиком:
refRemote.json- весь файл про загрузку внешних схем по HTTP;definitions.json- проверяет схему по метасхеме draft-07, доступной по внешней ссылке;format.jsonи каталогoptional/- необязательная часть спецификации.
Реализация учитывает несколько мест, где OneScript ведёт себя иначе, чем ожидает спецификация:
Ложь = 0иИстина = 1истинны. Сравнение значений всегда начинается со сравнения типов JSON, иначе булево прошло быtype: numberи совпало бы с нулём вenum.- Регулярные выражения по умолчанию регистронезависимы и многострочны. Оба флага снимаются явно, как требует ECMA 262.
СтрДлинасчитает единицы UTF-16.minLengthиmaxLengthсчитают кодовые точки, поэтому эмодзи - один символ, а не два.- Десятичная арифметика.
multipleOfпроверяется точным делением:0.0075 / 0.0001даёт ровно75, подбор эпсилон не нужен. - Свойство со значением
nullнеотличимо от отсутствующего по результатуПолучить(). Наличие свойства определяется перебором ключей, поэтомуrequiredиconst: nullработают верно.
Две границы задаёт сам тип Число (Decimal): литералы вне диапазона (1e308) платформа прочитать не может, а дробные литералы длиннее ~15 значащих цифр ПрочитатьJSON округляет. На данных, приходящих из реальных систем, это не проявляется.
Регулярные выражения выполняются движком .NET. Он совместим с ECMA 262 в объёме, который рекомендует спецификация, но отличается в мелочах - например $ совпадает и перед завершающим переводом строки.
opm install -l
oneunit execute -d ./tests