Skip to content

Repository files navigation

jsonschema

Валидатор 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, которое спецификация объявляет необязательным. ОстанавливатьсяНаПервойОшибке прекращает обход, как только найдено первое нарушение, - это заметно быстрее, когда нужен только вердикт.

Публичный API

Модуль JsonSchema

Метод Возвращает Описание
Проверить(Значение, Схема, Параметры = Неопределено) JsonSchemaResult Проверка со списком ошибок
Корректно(Значение, Схема, Параметры = Неопределено) Булево Проверка без подробностей
Скомпилировать(Схема, Параметры = Неопределено) Валидатор Разбор схемы для многократного применения
ПроверитьJson(ТекстJson, Схема, Параметры = Неопределено) JsonSchemaResult Разбор текста JSON и проверка
ИзJson(ТекстJson) Произвольный Разбор текста JSON во встроенные типы
ПоддерживаемыеФорматы() Массив Имена проверяемых значений format

Класс Валидатор

Новый Валидатор(Схема, Параметры = Неопределено), методы Проверить(Значение), Корректно(Значение), ПроверитьJson(ТекстJson).

Класс JsonSchemaResult

Член Тип Описание
Корректно Булево Итог проверки
Ошибки Массив Структуры с полями Путь, Ключевое, Сообщение, Значение
ПерваяОшибка() Структура Первая ошибка или Неопределено
Описание() Строка Человекочитаемый текст, по одной ошибке в строке
ВыброситьЕслиНекорректно() - Исключение, если значение не прошло проверку

Поддержка ключевых слов

Группа Ключевые слова Состояние
Общие 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 Игнорируются

Неизвестные ключевые слова игнорируются, как требует спецификация.

Ссылки $ref

Поддержаны указатели внутри текущего документа: # (корень) и #/путь/к/узлу. Реализован 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

Лицензия

MIT

About

Проверка значений по схеме draft-07: типы, комбинаторы, условия, $ref, ошибки с JSON Pointer

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages