Статьи

Проверка данных с помощью JSON-схемы, часть 2

В первой части этого руководства вы узнали, как создавать довольно сложные схемы, используя все доступные ключевые слова проверки. Многие реальные примеры данных JSON более сложны, чем наш пользовательский пример. Попытка поместить все требования к таким данным в один файл может привести к очень большой схеме, которая также может иметь много дубликатов.

Стандарт JSON-схемы позволяет разбивать схемы на несколько частей. Давайте рассмотрим пример данных для навигации по новостному сайту:

javascript { "level": 1, "parent_id": null, "visitors": "all", "color": "white", "pages": [ { "page_id": 1, "short_name": "home", "display_name": "Home", "url": "/home", "navigation": { "level": 2, "parent_id": 1, "color": "blue", "pages": [ { "page_id": 11, "short_name": "headlines", "display_name": "Latest headlines", "url": "/home/latest", "navigation": { "level": 3, "parent_id": 11, "color": "white", "pages": [ { "page_id": 111, "short_name": "latest_all", "display_name": "All", "url": "/home/latest" }, ... ] } }, { "page_id": 12, "short_name": "events", "display_name": "Events", "url": "/home/events" } ] } }, ... ] }

Структура навигации выше похожа на ту, которую вы можете увидеть на сайте http://dailymail.co.uk . Более полный пример вы можете увидеть в репозитории GitHub .

Структура данных сложна и рекурсивна, но схемы, описывающие эти данные, довольно просты:

navigation.json:

javascript { "$schema": "http://json-schema.org/draft-04/schema#", "id": "http://mynet.com/schemas/navigation.json#", "title": "Navigation", "definitions": { "positiveIntOrNull": { "type": ["null", "integer"], "minimum": 1 } }, "type": "object", "additionalProperties": false, "required": [ "level", "parent_id", "color", "pages" ], "properties": { "level": { "$ref": "defs.json#/definitions/positiveInteger" }, "parent_id": { "$ref": "#/definitions/positiveIntOrNull" }, "visitors": { "enum": [ "all", "subscribers", "age18" ] }, "color": { "$ref": "defs.json#/definitions/color" }, "pages": { "type": "array", "items": { "$ref": "page.json#" } } } }

page.json:

javascript { "$schema": "http://json-schema.org/draft-04/schema#", "id": "http://mynet.com/schemas/page.json#", "title": "Page", "type": "object", "additionalProperties": false, "required": [ "page_id", "short_name", "display_name", "path" ], "properties": { "page_id": { "$ref": "defs.json#/definitions/positiveInteger" }, "short_name": { "type": "string", "pattern": "^[a-z_]+$" }, "display_name": { "type": "string", "minLength": 1 }, "path": { "type": "string", "pattern": "^(?:/[a-z_\\-]+)+$" }, "color": { "$ref": "defs.json#/definitions/color" }, "navigation": { "$ref": "navigation.json#" } } }

defs.json:

javascript { "$schema": "http://json-schema.org/draft-04/schema#", "id": "http://mynet.com/schemas/defs.json#", "title": "Definitions", "definitions": { "positiveInteger": { "type": "integer", "minimum": 1 }, "color": { "anyOf": [ { "enum": [ "red", "green", "blue", "white" ] }, { "type": "string", "pattern": "^#(?:(?:[0-9a-fA-F]{1,2})){3}$" } ] } } }

Посмотрите на схемы выше и навигационные данные, которые они описывают (это действует в соответствии со схемой navigation.json ). Главное, на что следует обратить внимание, это то, что схема navigation.json ссылается на схему page.json которая в свою очередь ссылается на первую.

Код JavaScript для проверки записи пользователя по схеме может быть:

« `javascript var Ajv = require (‘ajv’); var ajv = Ajv ({allErrors: true, схемы: [require (‘./ navigation.json’), require (‘./ page.json’), require (‘./ defs.json’)]});

var validate = ajv.getSchema («http://mynet.com/schemas/navigation.json#»); var valid = validate (navigationData); if (! valid) console.log (validate.errors); « `

Все примеры кода доступны в репозитории GitHub .

Ajv , валидатор, используемый в примере, является самым быстрым валидатором JSON-Schema для JavaScript. Я создал его, поэтому собираюсь использовать его в этом уроке. В конце мы посмотрим, как он сравнивается с другими валидаторами, чтобы вы могли выбрать подходящий для вас.

См. Часть 1 руководства для получения инструкций по установке репозитория с заданиями и проверке ваших ответов.

Стандарт JSON-Schema позволяет повторно использовать повторяющиеся части схем, используя ссылки с ключевым словом $ ref . Как вы можете видеть из примера навигации, вы можете ссылаться на схему, которая находится:

  • в другом файле: используйте URI схемы, который определен в его свойстве «id»
  • в любой части другого файла: добавьте указатель JSON к ссылке на схему
  • в любой части текущей схемы: добавьте указатель JSON к «#»

Вы также можете ссылаться на всю текущую схему, используя «$ ref», равный «#» — это позволяет создавать рекурсивные схемы, ссылающиеся на себя.

Так что в нашем примере схема в navigation.json ссылается на:

  • схема page.json
  • definitions в схеме defs.json
  • определение positiveIntOrNull в той же схеме

Схема в page.json ссылается на:

  • вернуться к схеме navigation.json
  • также к definitions в файле defs.json

Стандарт требует, чтобы «ref» было единственным свойством в объекте, поэтому, если вы хотите применить ссылочную схему в дополнение к другой схеме, вы должны использовать ключевое слово «allOf» .

Выполните рефакторинг пользовательской схемы из первой части руководства, используя ссылки. Разделите схему на два файла: user.json и connection.json .

Поместите ваши схемы в файлы part2/task1/user.json и part2/task1/connection.json и запустите node part2/task1/validate чтобы проверить правильность ваших схем.

Указатель JSON — это стандарт, определяющий пути к частям файлов JSON. Стандарт описан в RFC6901 .

Этот путь состоит из сегментов (которые могут быть любой строкой), связанных с символом «/». Если сегмент содержит символы «~» или «/», их следует заменить на «~ 0» и «~ 1». Каждый сегмент означает свойство или индекс в данных JSON.

Если вы посмотрите на пример навигации, то «ref», который определяет свойство color — это «defs.json # / definitions / color», где «defs.json #» — это URI схемы, а «/ definitions / color» — это Указатель JSON. Он указывает на color свойства внутри definitions свойств.

Соглашение состоит в том, чтобы поместить все части схемы, которые используются в ссылках, в свойство definitions схемы (как вы можете видеть в примере). Хотя стандарт JSON-схемы резервирует ключевое слово definitions для этой цели, не обязательно помещать туда свои подсхемы. JSON-указатель позволяет вам обращаться к любой части файла JSON.

Когда указатели JSON используются в URI, все символы, которые недопустимы в URI, должны быть экранированы (в JavaScript может использоваться глобальная функция encodeURIComponent ).

JSON-указатели могут использоваться не только в JSON-схемах. Их можно использовать для представления пути в данных JSON к любому свойству или элементу. Вы можете использовать библиотеку json-pointer для доступа к объектам с помощью JSON-указателей.

Файл JSON ниже описывает структуру папок и файлов (имена папок начинаются с «/»):

javascript { "/": { "/documents": { "my_story~.rtf": { "type": "document", "application": ["Word", "TextEdit"], "size": 30476 }, ... }, "/system": { "/applications": { "Word": { "type": "executable", "size": 1725058307 }, ... } } } }

На какие указатели JSON указывают:

  • размер приложения «Word»,
  • размер документа «my_story ~ .rtf»,
  • имя второго приложения, которое может открыть документ «my_story ~ .rtf»?

Поместите свои ответы в part2/task2/json_pointers.json и запустите node part2/task2/validate чтобы проверить их.

Схемы обычно имеют свойство «id» верхнего уровня, которое имеет URI схемы. Когда в схеме используется «$ ref» , его значение рассматривается как URI, который разрешается относительно «идентификатора» схемы.

Разрешение работает так же, как браузер разрешает URI, которые не являются абсолютными — они разрешаются относительно URI схемы, который находится в его свойстве «id» . Если «$ ref» является именем файла, оно заменяет имя файла в «id» . В примере навигации идентификатор схемы навигации имеет вид "http://mynet.com/schemas/navigation.json#" , поэтому при "page.json#" ссылки "page.json#" полный URI схемы страницы становится "http://mynet.com/schemas/page.json#" (это » идентификатор « схемы page.json ).

Если бы «$ ref» схемы страницы представлял собой путь, например "/page.json" , то он был бы разрешен как "http://mynet.com/page.json#" . И "/folder/page.json" был бы решен как "http://mynet.com/folder/page.json#" .

Если «$ ref» начинается с символа «#», он обрабатывается как фрагмент хеша и добавляется к пути в «id» (заменяя в нем фрагмент хеша). В примере навигации ссылка "defs.json#/definitions/color" разрешается как "http://mynet.com/schemas/defs.json#/definitions/color" где "http://mynet.com/schemas/defs.json#" — это идентификатор схемы определений, а "/definitions/color" обрабатывается как указатель JSON внутри нее.

Если бы «$ ref» был полным URI с другим доменным именем, точно так же, как ссылки работают в браузере, он был бы разрешен как тот же полный URI.

Стандарт JSON-схемы позволяет вам использовать «id» внутри схемы для идентификации этих подсхем, а также для изменения базового URI, относительно которого будут разрешаться внутренние ссылки, — это называется «изменение области разрешения». Это, вероятно, одна из самых запутанных частей стандарта, и поэтому он не очень широко используется.

Я не рекомендовал бы чрезмерно использовать внутренние идентификаторы, за одним исключением ниже, по двум причинам:

  • Очень немногие валидаторы последовательно следуют стандарту и правильно разрешают ссылки, когда используются внутренние идентификаторы (Ajv полностью следует стандарту здесь).
  • Схемы становятся более сложными для понимания.

Мы все еще рассмотрим, как это работает, потому что вы можете столкнуться со схемами, которые используют внутренние идентификаторы, и есть случаи, когда их использование помогает структурировать ваши схемы.

Во-первых, давайте посмотрим на наш пример навигации. Большинство ссылок находятся в definitions объекта, и это делает ссылки довольно длинными. Есть способ сократить их, добавив идентификаторы в определения. Это обновленная схема defs.json :

javascript { "$schema": "http://json-schema.org/draft-04/schema#", "id": "http://mynet.com/schemas/defs.json#", "title": "Definitions", "definitions": { "positiveInteger": { "id": "#positiveInteger", "type": "integer", "minimum": 1 }, "color": { "id": "#color", "anyOf": [ { "enum": [ "red", "green", "blue", "white" ] }, { "type": "string", "pattern": "^#(?:(?:[0-9a-fA-F]{1,2})){3}$" } ] } } }

Теперь вместо ссылок "defs.json#/definitions/positiveInteger" и "defs.json#/definitions/color" , которые используются в схемах навигации и страниц, вы можете использовать более короткие ссылки: "defs.json#positiveInteger" и "defs.json#color" . Это очень распространенное использование внутренних идентификаторов, поскольку оно позволяет сделать ваши ссылки короче и более читабельными. Обратите внимание, что хотя этот простой случай будет правильно обрабатываться большинством валидаторов JSON-схемы, некоторые из них могут не поддерживать его.

Давайте посмотрим на более сложный пример с идентификаторами. Вот пример схемы JSON:

javascript { "id": "http://xyz/rootschema.json#", "definitions": { "bar": { "id": "#bar", "type": "string" } }, "subschema": { "id": "http://somewhere.else/completely.json#", "definitions": { "bar": { "id": "#bar", "type": "integer" } }, "type": "object", "properties": { "foo": { "$ref": "#bar" } } }, "type": "object", "properties": { "bar": { "$ref": "#/subschema" }, "baz": { "$ref": "#/subschema/properties/foo" }, "bax": { "$ref": "http://somewhere.else/completely.json#bar" } } }

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

Схема определяет объект со свойствами bar , baz и bax . Свойство bar должно быть объектом, действительным в соответствии с подсхемой, которая требует, чтобы его свойство foo действительным в соответствии со ссылкой "bar" . Поскольку подсхема имеет свой собственный «идентификатор» , полный URI для ссылки будет "http://somewhere.else/completely.json#bar" , поэтому он должен быть целым числом.

Теперь посмотрим на свойства bax и bax . Ссылки на них написаны по-другому, но они указывают на одну и ту же ссылку "http://somewhere.else/completely.json#bar" и они оба должны быть целыми числами. Хотя свойство baz указывает непосредственно на схему { "$ref": "#bar" } , оно все равно должно быть разрешено относительно идентификатора подсхемы, поскольку она находится внутри нее. Таким образом, объект ниже действителен в соответствии с этой схемой:

javascript { "bar": { "foo": 1 }, "baz": 2, "bax": 3 }

Многие валидаторы JSON-схем не справятся с этим правильно, поэтому идентификаторы, которые изменяют область разрешения, следует использовать с осторожностью.

Решение этой головоломки поможет вам лучше понять, как работают ссылки и изменение области действия разрешения. Ваша схема:

javascript { "id": "http://xyz/rootschema.json#", "title": "Task 3", "description": "Schema with references - create a valid data", "definitions": { "my_data": { "id": "#my_data", "type": "integer" } }, "schema1": { "id": "#foo", "allOf": [ { "$ref": "#my_data" } ] }, "schema2": { "id": "otherschema.json", "definitions": { "my_data": { "id": "#my_data", "type": "string" } }, "nested": { "id": "#bar", "allOf": [ { "$ref": "#my_data" } ] }, "alsonested": { "id": "t/inner.json#baz", "definitions": { "my_data": { "id": "#my_data", "type": "boolean" } }, "allOf": [ { "$ref": "#my_data" } ] } }, "schema3": { "id": "http://somewhere.else/completely#", "definitions": { "my_data": { "id": "#my_data", "type": "null" } }, "allOf": [ { "$ref": "#my_data" } ] }, "type": "object", "properties": { "foo": { "$ref": "#foo" }, "bar": { "$ref": "otherschema.json#bar" }, "baz": { "$ref": "t/inner.json#baz" }, "bax": { "$ref": "http://somewhere.else/completely#" }, "quux": { "$ref": "#/schema3/allOf/0" } }, "required": [ "foo", "bar", "baz", "bax", "quux" ] }

Создайте объект, который действителен в соответствии с этой схемой.

Поместите свой ответ в part2/task3/valid_data.json и запустите node part2/task3/validate чтобы проверить его.

До сих пор мы смотрели на различные схемы, ссылающиеся друг на друга, не обращая внимания на то, как они загружаются в валидатор.

Один из подходов состоит в том, чтобы все подключенные схемы были предварительно загружены, как в примере навигации выше. Но бывают ситуации, когда это либо непрактично, либо невозможно — например, если необходимая схема предоставляется другим приложением или вы заранее не знаете всех возможных схем, которые могут понадобиться.

В таких случаях валидатор может загружать ссылочные схемы в то время, когда данные проверяются. Но это сделало бы процесс проверки медленным. Ajv позволяет вам скомпилировать схему в валидирующую функцию, асинхронно загружая отсутствующие ссылочные схемы в процессе. Сама проверка будет по-прежнему синхронной и быстрой.

Например, если схемы навигации были доступны для загрузки с URI в их идентификаторах, код для проверки данных по схеме навигации может быть следующим:

« `javascript var Ajv = require (‘ajv’); var request = require (‘request’); var ajv = Ajv ({allErrors: true, loadSchema: loadSchema});

var _validateNav; // функция проверки будет кэширована здесь после загрузки и компиляции

function validateNavigation (data, callback) {if (_validateNav) setTimeout (_validate); loadSchema (‘http://mynet.com/schemas/navigation.json’, функция (err, схема) {if (err) возвращает обратный вызов (err); ajv.compileAsync (схема, функция (err, v) {if ( err) обратный вызов (err); else {_validateNav = v; _validate ();}});});

function _validate () {var valid = _validateNav (data); обратный вызов (null, {valid: valid, errors: _validateNav.errors}); }}

function loadSchema (uri, callback) {request.json (uri, function (err, res, body)) {if (err || res.statusCode> = 400) обратный вызов (err || new Error (‘Ошибка загрузки:’ + res .statusCode)); else callback (null, body);}); } « `

Код определяет функцию validateNavigation которая загружает схему и компилирует функцию проверки, когда она вызывается в первый раз, и всегда возвращает результат проверки посредством обратного вызова. Существуют различные способы его улучшения, от предварительной загрузки и компиляции схемы до ее первого использования, до учета того факта, что функция может вызываться несколько раз, прежде чем ей удалось кэшировать схему ( ajv.compileAsync уже обеспечивает что схема всегда запрашивается только один раз).

Теперь рассмотрим новые ключевые слова, предлагаемые для версии 5 стандарта JSON-схемы.

Хотя эти предложения не были завершены в качестве стандартного проекта, они могут использоваться сегодня — валидатор Ajv реализует их. Они существенно расширяют возможности проверки с помощью JSON-схемы, поэтому их стоит использовать.

Чтобы использовать все эти ключевые слова с Ajv, вам нужно использовать опцию v5: true .

Эти ключевые слова добавлены для удобства.

Ключевое слово «константа» требует, чтобы данные были равны значению ключевого слова. Без этого ключевого слова его можно было бы получить с помощью ключевого слова enum с одним элементом в массиве элементов.

Эта схема требует, чтобы данные были равны 1:

javascript { "constant": 1 }

Ключевое слово «содержит» требует, чтобы некоторый элемент массива соответствовал схеме в этом ключевом слове. Это ключевое слово относится только к массивам; любой другой тип данных будет действительным в соответствии с ним. Несколько сложнее выразить это требование, используя только ключевые слова из версии 4, но это возможно.

Эта схема требует, чтобы, если данные были массивом, то хотя бы один из их элементов был целым числом:

javascript { "contains": { "type": "integer" } }

Это эквивалентно этому:

javascript { "not": { "type": "array", "items": { "not": { "type": "integer" } } } }

Чтобы эта схема была действительной, либо данные не должны быть массивом, либо все элементы не должны быть целыми (т. Е. Некоторые элементы должны быть целыми).

Обратите внимание, что ключевое слово «содержит» и эквивалентная схема, приведенная выше, потерпят неудачу, если данные будут пустым массивом

Это ключевое слово предлагается в качестве замены для patternProperties . Это позволяет ограничить количество свойств, соответствующих шаблону, который должен существовать в объекте. Ajv поддерживает как «patternGroups», так и «patternProperties» в режиме v5, потому что первое намного более многословно, и если вы не хотите ограничивать количество свойств, вы можете предпочесть использование второго.

Например, схема:

javascript { "patternGroups": { "^[az]+$": { "schema": { "type": "string" } }, "^[0-9]+$": { "schema": { "type": "number" } } } }

эквивалентно этой схеме:

javascript { "patternProperties": { "^[az]+$": { "type": "string" }, "^[0-9]+$": { "type": "number" } } }

Они оба требуют, чтобы у объекта были только свойства с ключами, состоящими только из строчных букв со значениями типа string, и с ключами, состоящими только из чисел со значениями типа number. Они не требуют никакого количества таких свойств и не ограничивают максимальное количество. Вот что вы можете сделать с «patternGroups» :

javascript { "patternGroups": { "^[az]+$": { "minimum": 1, "maximum": 3, "schema": { "type": "string" } }, "^[0-9]+$": { "minimum": 1, "schema": { "type": "number" } } } }

Приведенная выше схема имеет дополнительные требования: должно быть как минимум одно свойство, соответствующее каждому шаблону, и не более трех свойств, ключи которых содержат только буквы.

Вы не можете достичь того же с «patternProperties» .

Эти ключевые слова вместе с «exclusiveFormatMaximum» / «exclusiveFormatMinimum» позволяют вам устанавливать ограничения для времени, даты и, возможно, других строковых значений, формат которых требуется с ключевым словом «format» .

Эта схема требует, чтобы данные были датой и были больше или равны 1 января 2016 года:

javascript { "format": "date", "formatMinimum": "2016-01-01" }

Ajv поддерживает сравнение форматированных данных для форматов «дата», «время» и «дата-время», и вы можете определить пользовательские форматы, которые будут поддерживать ограничения, с помощью ключевых слов «formatMaximum» / «formatMaximum» .

Хотя все предыдущие ключевые слова позволили вам лучше выразить то, что было возможно без них, или немного расширили возможности, они не изменили декларативный и статический характер схемы. Это ключевое слово позволяет сделать проверку динамической и зависящей от данных. Он содержит несколько случаев if-then.

Это проще объяснить на примере:

javascript { "switch": [ { "if": { "minimum": 50 }, "then": { "multipleOf": 5 } }, { "if": { "minimum": 10 }, "then": { "multipleOf": 2 } }, { "if": { "maximum": 4 }, "then": false } ] }

Приведенная выше схема последовательно проверяет данные по подсхемам в ключевых словах «если», пока одно из них не пройдет проверку. Когда это происходит, он проверяет схему в ключевом слове «then» в том же объекте — это будет результатом проверки всей схемы. Если значение «then» равно false , проверка сразу завершается неудачей.

Таким образом, приведенная выше схема требует, чтобы значение было:

  • больше или равно 50 и кратно 5
  • или между 10 и 49 и кратным 2
  • или между 5 и 9

Этот конкретный набор требований может быть выражен без ключевого слова switch, но есть более сложные случаи, когда это невозможно.

Создайте схему, эквивалентную последнему примеру выше, без использования ключевого слова switch.

Поместите свой ответ в part2/task4/no_switch_schema.json и запустите node part2/task4/validate чтобы проверить его.

Регистр ключевых слов «switch» также может содержать ключевое слово «continue» с логическим значением. Если это значение равно true , проверка будет продолжена после успешного совпадения схемы if с успешной проверкой схемы then . Это похоже на переход к следующему случаю в операторе переключения JavaScript, хотя в JavaScript переключение является поведением по умолчанию, а для ключевого слова «switch» требуется явная инструкция «continue» . Это еще один простой пример с инструкцией «продолжить» :

javascript "schema": { "switch": [ { "if": { "minimum": 10 }, "then": { "multipleOf": 2 }, "continue": true }, { "if": { "minimum": 20 }, "then": { "multipleOf": 5 } } ] }

Если первое условие «если» выполнено и требование «тогда» выполнено, проверка продолжит проверку второго условия.

Ключевое слово «$ data» еще больше расширяет возможности JSON-схемы и делает проверку более динамичной и зависящей от данных. Это позволяет вам помещать значения из некоторых свойств данных, элементов или ключей в определенные ключевые слова схемы.

Например, эта схема определяет объект с двумя свойствами, где, если оба определены, «больше» должно быть больше или равно «меньше» — значение в «меньше» используется как минимум для «больше»:

javascript "schema": { "properties": { "smaller": {}, "larger": { "minimum": { "$data": "1/smaller" } } } }

Ajv реализует ссылку «$ data» для большинства ключевых слов, значения которых не являются схемами. Он не проходит проверку, если ссылка «$ data» указывает на неверный тип, и завершается успешно, если указывает на неопределенное значение (или если путь не существует в объекте).

Так что же является строковым значением в ссылке «$ data» ? Это похоже на JSON-указатель, но это не совсем так. Это относительный JSON-указатель, определенный этим стандартным проектом .

Он состоит из целого числа, которое определяет, сколько раз поиск должен пройти по объекту (1 в приведенном выше примере означает прямой родитель), за которым следует «#» или указатель JSON.

Если за номером следует «#», то значение, которое JSON-указатель разрешает, будет именем свойства или индексом элемента, который имеет объект. Таким образом, «0 #» вместо «1 / меньший» будет преобразован в строку «больше», а «1 #» будет недопустимым, так как все данные не являются членами какого-либо объекта или массива. Эта схема:

javascript { "type": "object", "patternProperties": { "^date$|^time$": { "format": { "$data": "0#" } } } }

эквивалентно этому:

javascript { "type": "object", "properties": { "date": { "format": "date" }, "time": { "format": "time" } } }

потому что {«$ data»: «0 #»} заменяется именем свойства.

Если за номером в указателе следует JSON-указатель, то этот JSON-указатель разрешается, начиная с родительского объекта, к которому относится этот номер. Вы можете увидеть, как это работает в первом «меньшем» / «большем» примере.

Давайте снова посмотрим на наш пример навигации. Одним из требований, которые вы можете видеть в данных, является то, что свойство page_id в объекте страницы всегда равно parent_id в содержащемся объекте навигации. Мы можем выразить это требование в схеме page.json используя ссылку «$ data» :

javascript { "$schema": "http://json-schema.org/draft-04/schema#", "id": "http://mynet.com/schemas/page.json#", ... "switch": [{ "if": { "required": [ "navigation" ] }, "then": { "properties": { "page_id": { "constant": { "$data": "1/navigation/parent_id" } } } } }] }

Ключевое слово «switch», добавленное в схему страницы, требует, чтобы, если у объекта страницы было свойство navigation то значение свойства page_id должно совпадать со значением свойства parent_id в объекте навигации. То же самое может быть достигнуто без ключевого слова «switch» , но оно менее выразительно и содержит дублирование:

javascript { ... "anyOf": [ { "not": { "required": [ "navigation" ] } }, { "required": [ "navigation" ], "properties": { "page_id": { "constant": { "$data": "1/navigation/parent_id" } } } } ] }

Примеры относительных JSON-указателей могут быть полезны.

Используя ключевые слова v5, определите схему для объекта с двумя необходимыми list свойств и order . Список должен быть массивом, который имеет до пяти чисел. Все элементы должны быть числами, и они должны быть упорядочены в порядке возрастания или убывания, что определяется order свойств order который может быть "asc" или "desc" .

Например, это допустимый объект:

javascript { "list": [ 1, 3, 3, 6, 9 ], "order": "asc" }

и это неверно:

javascript { "list": [ 9, 7, 3, 6, 2 ], "order": "desc" }

Поместите свой ответ в part2/task5/schema.json и запустите node part2/task5/validate чтобы проверить его.

Как бы вы создали схему с теми же условиями, но для списка неограниченного размера?

Мы рассмотрели новые ключевые слова, предлагаемые для версии 5 стандарта JSON-схемы. Вы можете использовать их сегодня, но иногда вы можете хотеть большего. Если вы выполнили задачу 5, вы, вероятно, заметили, что некоторые требования сложно выразить с помощью JSON-схемы.

Некоторые валидаторы, включая Ajv, позволяют вам определять пользовательские ключевые слова. Пользовательские ключевые слова:

  • позволяют создавать сценарии проверки, которые не могут быть выражены с использованием JSON-схемы
  • упростить ваши схемы
  • помочь вам внести большую часть логики проверки в ваши схемы
  • сделать ваши схемы более выразительными, менее многословными и приблизиться к области применения

Один из разработчиков, который использует Ajv, написал на GitHub:

«Ajv с пользовательскими ключевыми словами очень помог нам с проверкой бизнес-логики в нашем бэкэнде. Мы объединили целую кучу проверок на уровне контроллера в JSON-схему с пользовательскими ключевыми словами. Чистый эффект намного лучше, чем написание индивидуального кода проверки ».

При расширении стандарта JSON-схемы с помощью настраиваемых ключевых слов вам следует помнить о переносимости и понимании ваших схем. Вам придется поддерживать эти пользовательские ключевые слова на других платформах и правильно документировать эти ключевые слова, чтобы каждый мог понять их в ваших схемах.

Наилучшим подходом здесь является определение новой мета-схемы, которая будет расширением черновой мета-схемы 4 или мета-схемы «предложения v5», которая будет включать как проверку ваших дополнительных ключевых слов, так и их описание. Тогда ваши схемы, использующие эти пользовательские ключевые слова, должны будут установить для свойства $schema URI новой мета-схемы.

Теперь, когда вы получили предупреждение, мы углубимся и определим пару пользовательских ключевых слов, используя Ajv.

Ajv предоставляет четыре способа определения пользовательских ключевых слов, которые вы можете увидеть в документации . Мы рассмотрим два из них:

  • используя функцию, которая компилирует вашу схему в функцию проверки
  • используя макро-функцию, которая берет вашу схему и возвращает другую схему (с или без пользовательских ключевых слов)

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

Вот как должна выглядеть схема:

javascript { "range": [5, 10], "exclusiveRange": true }

где эксклюзивный диапазон необязателен, конечно. Код для определения этого ключевого слова приведен ниже:

« `javascript ajv.addKeyword (‘range’, {type: ‘number’, compile: compileRange}); ajv.addKeyword ( ‘exclusiveRange’); // это необходимо для резервирования ключевого слова

function compileRange (schema, parentSchema) {var min = schema [0]; var max = схема [1];

вернуть parentSchema.exclusiveRange === правда? функция (данные) {возвращаемые данные> min && data <max; }: функция (данные) {возвращаемые данные> = min && data <= max; }} « `

Вот и все! После этого кода вы можете использовать ключевое слово range в ваших схемах:

« `javascript var schema = {« range »: [5, 10],« exclusiveRange »: true};

var validate = ajv.compile (схема);

console.log (Validate (5)); // false console.log (validate (5.1)); // true console.log (validate (9.9)); // true console.log (validate (10)); // false « `

Объект, переданный в addKeyword является определением ключевого слова. Он может содержать тип (или типы в виде массива), к которому применяется ключевое слово. Функция компиляции вызывается с параметрами schema и parentSchema и должна возвращать другую функцию, которая проверяет данные. Это делает его почти таким же эффективным, как и нативные ключевые слова, потому что схема анализируется во время ее компиляции, но при проверке стоит дополнительный вызов функции.

Ajv позволяет избежать этих издержек с помощью ключевых слов, которые возвращают код (в виде строки), который будет включен в функцию проверки, но он довольно сложный, поэтому мы не будем рассматривать его здесь. Более простой способ — использовать макро-ключевые слова — вам нужно определить функцию, которая берет схему и возвращает другую схему.

Ниже приведена реализация ключевого слова range с макро-функцией:

« `javascript ajv.addKeyword (‘range’, {type: ‘number’, macro: macroRange});

function macroRange (схема, parentSchema) {var resultSchema = {«минимум»: схема [0], «максимум»: схема [1]};

if (parentSchema.exclusiveRange === true) {resultSchema.exclusiveMimimum = resultSchema.exclusiveMaximum = true; }

вернуть resultSchema; } « `

Вы можете видеть, что функция просто возвращает новую схему, которая эквивалентна ключевому слову range которое использует ключевые слова maximum и minimum .

Давайте также посмотрим, как мы можем создать мета-схему, которая будет включать ключевое слово range . В качестве отправной точки мы будем использовать черновую мета-схему 4:

javascript { "id": "http://mynet.com/schemas/meta-schema-with.range.json#", "$schema": "http://json-schema.org/draft-04/schema#", "allOf": [ { "$ref": "http://json-schema.org/draft-04/schema#" }, { "properties": { "range": { "description": "1st item is minimum, 2nd is maximum", "type": "array", "items": [ { "type": "number" }, { "type": "number" } ], "additionalItems": false }, "exclusiveRange": { "type": "boolean", "default": false } }, "dependencies": { "exclusiveRange": [ "range" ] } } ] }

Если вы хотите использовать ссылки «$ data» с ключевым словом range , вам придется расширить мета-схему «предложения v5» , включенную в Ajv (см. Ссылку выше), чтобы эти ссылки могли быть значениями range и exclusiveRange , И хотя наша первая реализация не будет поддерживать ссылки «$ data» , вторая с макро-функцией будет поддерживать их.

Теперь, когда у вас есть мета-схема, вам нужно добавить ее в Ajv и использовать в схемах с помощью ключевого слова range :

« `javascript ajv.addMetaSchema (require (‘./ meta-schema-with-range.json’));

var schema = {«$ schema»: «http://mynet.com/schemas/meta-schema-with-range.json#», «range»: [5, 10], «exclusiveRange»: true};

var validate = ajv.compile (схема); « `

Приведенный выше код вызвал бы исключение, если бы недопустимые значения были переданы в range или exclusiveRange .

Предположим, что вы определили ключевое слово jsonPointers которое применяет схемы к глубоким свойствам, определенным указателями JSON, которые указывают на данные, начиная с текущего. Это ключевое слово полезно с ключевым словом switch как оно позволяет вам определять требования к глубоким свойствам и элементам. Например, эта схема использует ключевое слово jsonPointers :

javascript { "jsonPointers": { "0/books/2/title": { "pattern": "json|Json|JSON" }, } }

эквивалентно:

javascript { "properties": { "books": { "items": [ {}, {}, { "properties": { "title": { "pattern": "json|Json|JSON" } } } ] } } }

Предположим, что вы также определили ключевое слово requiredJsonPointers которое работает аналогично required но с JSON-указателями вместо свойств.

Если хотите, вы можете сами определить эти ключевые слова или посмотреть, как они определены в файле part2/task6/json_pointers.js .

Ваша задача: используя ключевые слова jsonPointers и requiredJsonPointers , определите ключевое слово select , которое похоже на оператор switch JavaScript и имеет следующий синтаксис (в otherwise и fallthrough являются необязательными):

javascript { "select": { "selector": "<relative JSON-pointer that starts from '0/'>", "cases": [ { "case": <value1>, "schema": { <schema1> }, "fallthrough": true }, { "case": <value2>, "schema": { <schema2> } }, ... ], "otherwise": { <defaultSchema> } } }

Этот синтаксис допускает значения любого типа. Обратите внимание, что fallthrough в ключевом слове switch отличается от continue . fallthrough применяет схему из следующего случая к данным без проверки того, что селектор равен значению из следующего случая (так как он, скорее всего, не равен).

Поместите свои ответы в part2/task6/select_keyword.js и part2/task6/v5-meta-with-select.json и запустите node part2/task6/validate чтобы проверить их.

Бонус 1: улучшите вашу реализацию, чтобы также поддерживать этот синтаксис:

javascript { "select": { "selector": "<relative JSON-pointer that starts from '0/'>", "cases": { "<value1>": { <schema1> }, "<value2>": { <schema2> }, ... }, "otherwise": { <defaultSchema> } } }

Он может быть использован, если все значения являются разными строками, и нет никакого fallthrough .

Бонус 2: расширить мета-схему «предложения v5», включив это ключевое слово.

В дополнение к проверке данных, JSON-схемы могут использоваться для:

  • создать пользовательский интерфейс
  • генерировать данные
  • изменить данные

Вы можете посмотреть библиотеки, которые генерируют пользовательский интерфейс и данные, если вам это интересно. Мы не будем его изучать, поскольку это выходит за рамки этого урока.

Мы рассмотрим использование JSON-схемы для изменения данных во время их проверки.

Одной из распространенных задач при проверке данных является удаление дополнительных свойств из данных. Это позволяет санировать данные перед их передачей в логику обработки без сбоя проверки схемы:

« `javascript var ajv = Ajv ({removeAdditional: true});

var schema = {«type»: «object», «properties»: {«foo»: {«type»: «string»}}, «AdditionalProperties»: false};

var validate = ajv.compile (схема);

var data: {foo: 1, bar: 2};

console.log (Validate (данные)); // true console.log (data); // {foo: 1}; « `

Без опции removeAdditional проверка не удалась бы, так как есть дополнительная bar свойств, которая не разрешена схемой. С этой опцией проверка проходит, и свойство удаляется из объекта.

Когда removeAdditional параметра removeAdditional равно true , дополнительные свойства удаляются, только если ключевое слово removeAdditional значение false. Ajv также позволяет вам удалить все дополнительные свойства, независимо от ключевого слова AdditionalProperties, или дополнительных свойств, которые не проходят проверку (если ключевое слово AdditionalProperties является схемой). Пожалуйста, посмотрите документацию Ajv для получения дополнительной информации.

Стандарт JSON-схемы определяет ключевое слово «default», которое содержит значение, которое должны иметь данные, если оно не определено в проверенных данных. Ajv позволяет назначать такие значения по умолчанию в процессе проверки:

« `javascript var ajv = Ajv ({useDefaults: true});

var schema = {«type»: «object», «properties»: {«foo»: {«type»: «number»}, «bar»: {«type»: «string», «default»: «baz» »}},« Обязательный »: [« foo »,« bar »]};

var data = {«foo»: 1};

var validate = ajv.compile (схема);

console.log (Validate (данные)); // true console.log (data); // {«foo»: 1, «bar»: «baz»} « `

Без опции useDefaults проверка не удалась бы, так как в проверяемом объекте нет обязательной bar свойств. С этой опцией проверка проходит, и к объекту добавляется свойство со значением по умолчанию.

«Type» является одним из наиболее часто используемых ключевых слов в JSON-схемах. Когда вы проверяете вводимые пользователем данные, все свойства данных, которые вы получаете из форм, обычно являются строками. Ajv позволяет вам приводить данные к типам, указанным в схеме, как для проверки, так и для использования правильно введенных данных:

« `javascript var ajv = Ajv ({coerceTypes: true}); var schema = {«type»: «object», «properties»: {«foo»: {«type»: «number»}, «bar»: {«type»: «boolean»}}, «required»: [«Foo», «bar»]};

var data = {«foo»: «1», «bar»: «false»};

var validate = ajv.compile (схема);

console.log (Validate (данные)); // true console.log (data); // {«foo»: 1, «bar»: false} « `

Существует более десяти активно поддерживаемых валидаторов JavaScript. Какой из них вы должны использовать?

Вы можете увидеть показатели производительности и того, как разные валидаторы проходят набор тестов из стандарта JSON-схемы в проекте json-schema-benchmark .

У некоторых валидаторов есть и отличительные особенности, которые могут сделать их наиболее подходящими для вашего проекта. Я буду сравнивать некоторые из них ниже.

Эти два валидатора очень быстрые и имеют очень простые интерфейсы. Оба компилируют схемы в функции JavaScript, как это делает Ajv.

Их недостаток в том, что они оба имеют ограниченную поддержку удаленных ссылок.

Это единственная в своем роде библиотека, где проверка JSON-схемы является почти побочным эффектом.

Он построен как универсальный и легко расширяемый процессор / итератор JSON-схемы, который можно использовать для создания всевозможных инструментов, использующих JSON-схему: генераторы пользовательского интерфейса, шаблоны и т. Д.

В него уже включен относительно быстрый валидатор JSON-схемы.

Тем не менее, он вообще не поддерживает удаленные ссылки.

Самый медленный в группе быстрых валидаторов, он имеет полный набор функций, с ограниченной поддержкой удаленных ссылок.

Где он действительно сияет, так это его реализация ключевого слова по default . Хотя большинство валидаторов имеют ограниченную поддержку этого ключевого слова (Ajv не является исключением), в Themis очень сложная логика применения значений по умолчанию с откатами внутри составных ключевых слов, таких как anyOf .

С точки зрения производительности этот очень зрелый валидатор находится на границе между быстрым и медленным валидаторами. Вероятно, это был один из самых быстрых до появления нового поколения скомпилированных валидаторов (все вышеперечисленное и Ajv).

Он проходит почти все тесты из набора тестов JSON-схемы для валидаторов и имеет довольно тщательную реализацию удаленных ссылок.

Он имеет большой набор параметров, позволяющих настроить поведение по умолчанию для многих ключевых слов схемы JSON (например, не принимать пустые массивы в качестве массивов или пустых наборов строк) и наложить дополнительные требования на схемы JSON (например, требуется minLengthключевое слово для строки).

Я думаю, что в большинстве случаев изменение поведения схемы и включение запросов к другим службам в JSON-схеме — неправильные действия. Но бывают случаи, когда возможность сделать это сильно упрощается.

Это один из самых старых (и самых медленных) валидаторов, поддерживающих версию 4 стандарта. Таким образом, это часто выбор по умолчанию для многих проектов.

Если вы используете его, очень важно понять, как он сообщает об ошибках и пропущенных ссылках, и правильно его настроить, в противном случае вы получите много ложных срабатываний (т. Е. Проверка проходит с неверными данными или неразрешенными удаленными ссылками).

Форматы не включены по умолчанию, но они доступны в виде отдельной библиотеки.

Я написал Ajv, потому что все существующие валидаторы были либо быстрыми, либо соответствовали стандарту (особенно в отношении поддержки удаленных ссылок), но не оба. Айв заполнил этот пробел.

На данный момент это единственный валидатор, который:

  • проходит все тесты и полностью поддерживает удаленные ссылки
  • поддерживает проверочные ключевые слова, предложенные для версии 5 стандарта, и $dataссылки
  • поддерживает асинхронную проверку пользовательских форматов и ключевых слов

У него есть опции для изменения процесса проверки и изменения проверенных данных (фильтрация, назначение значений по умолчанию и типы принуждения — см. Примеры выше).

Я думаю, что лучший подход — это попробовать несколько и выбрать тот, который лучше всего подходит для вас.

Я написал json-schema -olidate , который предоставляет коллекцию адаптеров, которые объединяют интерфейсы 12 валидаторов JSON-схемы. Используя этот инструмент, вы можете тратить меньше времени на переключение между валидаторами. Я рекомендую удалить его, как только вы решите, какой валидатор использовать, так как его сохранение негативно скажется на производительности.

Это оно!Я надеюсь, что этот урок был полезен. Вы узнали о:

  • структурирование ваших схем
  • используя ссылки и идентификаторы
  • используя ключевые слова проверки и ссылку на $ data из предложений версии 5
  • загрузка удаленных схем асинхронно
  • определение пользовательских ключевых слов
  • изменение данных в процессе проверки
  • преимущества и недостатки различных валидаторов JSON-схем

Спасибо за прочтение!