Многие сервисы и приложения (особенно веб-сервисы) принимают данные в древовидном виде. Например, такую форму имеют данные, поступающие через JSON-PRC, JSON-REST, PHP-GET/POST. Естественно, появляется задача валидировать их структуру. Существует много вариантов решения этой задачи, начиная от нагромождения if-ов в контроллерах и заканчивая классами, реализующими валидацию по разнообразным конфигурациям. Чаще всего для решения этой задачи требуется рекурсивный валидатор, работающий со схемами данных, описанными по определённому стандарту. Одним из таких стандартов является JSON-Schema, рассмотрим его поближе.
JSON-schema — это стандарт описания структур данных в формате JSON, разрабабываемый на основе XML-Schema, драфт можно найти здесь (далее описанное будет соответствовать версии 03). Схемы, описанные этим стандартом, имеют MIME «application/schema+json». Стандарт удобен для использования при валидации и документировании структур данных, состоящих из чисел, строк, массивов и структур типа ключ-значение (которые, в зависимости от языка программирования, могут называться: объект, словарь, хэш-таблица, ассоциативный массив или карта, далее будет использоваться название «объект» или «object»). На данный момент имеются полные и частичные реализации для разных платформ и языков, в частности javascript, php, ruby, python, java.
Схема
Схема является JSON-объектом, предназначенным для описания каких-либо данных в формате JSON. Свойства этого объекта не являются обязательными, каждое их них является инструкцией определённого правила валидации (далее — правило). Прежде всего, схема может ограничивать тип данных (правило type или disallow, может быть как строкой, так и массивом):
- string (строка)
- number (число, включая все действительные числа)
- integer (целое число, является подмножеством number)
- boolean (true или false)
- object (объект, в некоторых языках зовётся ассоциативным массивом, хэшем, хэш-таблицей, картой или словарём)
- array (массив)
- null («ничего», возможно только значение null)
- any (любой тип, включая null)
Далее, в зависимости от типа проверяемых данных, применяются дополнительные правила. Например, если проверяемые данные являются числом, к нему могут быть применены minimum, maximum, divisibleBy. Если проверяемые данные являются массивом, в силу вступают правила: minItems, maxItems, uniqueItems, items. Если проверяемые данные являются строкой, применяюся: pattern, minLength, maxLength. Если же проверяется объект, рассматриваются правила: properties, patternProperties, additionalProperties.
Помимо специфичных для типа правил, есть дополнительные обобщённые правила, такие как required и format, а так же описательные правила, такие как id, title, description, $schema. Спецификация определяет несколько микроформатов, таких как: date-time (ISO 8601), date, time, utc-millisec, regex, color (W3C.CR-CSS21-20070719), style (W3C.CR-CSS21-20070719), phone, uri, email, ip-address (V4), ipv6, host-name, которые могут дополнительно проверяться, если определены и поддерживаются текущей реализацией. Более детально с этими и другими правилами можно ознакомиться в спецификации.
Поскольку схема является JSON-объектом, она тоже может быть проверена соответствующей схемой. Схема, которой соответствует текущая схема, записывается в атрибуте $schema. По нему можно определить версию драфта, который был использован для написания схемы. Найти эти схемы можно здесь.
Одной из самых мощных и привлекательных функций JSON-Schema является возможность из схемы ссылаться на другие схемы, а так же наследовать (расширять) схемы (с помощью ссылок JSON-Ref). Делается это с помощью id, extends и $ref. При расширении схемы нельзя переопределять правила, только дополнять их. При работе валидатора к проверяемым данным должны применяться все правила из родительской и дочерней схемы. Рассмотрим далее на примерах.
Примеры
Допустим, есть информация о товарах. У каждого товара есть имя. Это строка от 3 до 50 символов, без пробелов на концах. Определим схему для имени товара:
{ "$schema": "http://json-schema.org/draft-03/schema#", // ид схемы для этой схемы "id": "urn:product_name#", "type": "string", "pattern": "^\\S.*\\S$", "minLength": 3, "maxLength": 50, }
Отлично, теперь этой схемой можно описывать или валидировать любую строку на соответствие имени товара. Далее, у товара есть неотицательная цена, тип (‘phone’ или ‘notebook’), и поддержка wi-fi n и g. Определим схему для товара:
{ "$schema":"http://json-schema.org/draft-03/schema#", "id": "urn:product#", "type": "object", "additionalProperties": false, "properties": { "name": { "extends": {"$ref": "urn:product_name#"}, "required": true }, "price": { "type": "integer", "min": 0, "required": true }, "type": { "type": "string", "enum": ["phone", "notebook"], "required": true }, "wi_fi": { "type": "array", "items": { "type": "string", "enum": ["n", "g"] }, "uniqueItems": true } } }
В данной схеме используется ссылка на предыдущую схему и расширение её правилом required. Этого нельзя делать в предыдущей схеме, потому что где-нибудь имя может быть необязательным, а все правила будут применяться.
Производительность
Производительность валидатора на основе JSON-Schema, разумеется, развисит от реализации валидатора и полноты поддержки правил. Сделаем тест на nodejs и наиболее «полного» валидатора JSV (установить можно через «npm install JSV»). Сначала сгенерируем тысячу разных продуктов с невалидными свойствами, затем прогоним их через валидатор. После этого покажем количество ошибок каждого типа.
var jsv = require('JSV').JSV.createEnvironment(); console.time('load schemas'); jsv.createSchema( { "$schema": "http://json-schema.org/draft-03/schema#", "id": "urn:product_name#", "type": "string", "pattern": "^\\S.*\\S$", "minLength": 3, "maxLength": 50, } ); jsv.createSchema( { "$schema":"http://json-schema.org/draft-03/schema#", "id": "urn:product#", "type": "object", "additionalProperties": false, "properties": { "name": { "extends": {"$ref": "urn:product_name#"}, "required": true }, "price": { "type": "integer", "min": 0, "required": true }, "type": { "type": "string", "enum": ["phone", "notebook"], "required": true }, "wi_fi": { "type": "array", "items": { "type": "string", "enum": ["n", "g"] }, "uniqueItems": true } } } ); console.timeEnd('load schemas'); console.time('prepare data'); var i, j; var product; var products = []; var names = []; for (i = 0; i < 1000; i++) { product = { name: 'product ' + i }; if (Math.random() < 0.05) { while (product.name.length < 60) { product.name += 'long'; } } names.push(product.name); if (Math.random() < 0.95) { product.price = Math.floor(Math.random() * 200 - 2); } if (Math.random() < 0.95) { product.type = ['notebook', 'phone', 'something'][Math.floor(Math.random() * 3)]; } if (Math.random() < 0.5) { product.wi_fi = []; for (j = 0; j < 3; j++) { if (Math.random() < 0.5) { product.wi_fi.push(['g', 'n', 'a'][Math.floor(Math.random() * 3)]); } } } products.push(product); } console.timeEnd('prepare data'); var errors; var results = {}; var schema; var message; schema = jsv.findSchema('urn:product_name#'); console.time('names validation'); for (i = 0; i < names.length; i++) { errors = schema.validate(names[i]).errors; for (j = 0; j < errors.length; j++) { message = errors[j].message; if (!results.hasOwnProperty(message)) { results[message] = 0; } results[message]++; } } console.timeEnd('names validation'); console.dir(results); results = {}; schema = jsv.findSchema('urn:product#'); console.time('products validation'); for (i = 0; i < products.length; i++) { errors = schema.validate(products[i]).errors; for (j = 0; j < errors.length; j++) { message = errors[j].message; if (!results.hasOwnProperty(message)) { results[message] = 0; } results[message]++; } } console.timeEnd('products validation'); console.dir(results);
Результаты для 1000 проверок вполне удовлетворительные.
На моем ноутбуке (MBA, OSX, 1.86 GHz Core2Duo):
names validation: 180ms
products validation: 743ms
Заключение
JSON-Schema — достаточно удобный инструмент для документирования структур данных и конфигурирования автоматических валидаторов внешних данных в приложениях. Выглядит проще и читабельнее, чем XML Schema, при этом занимает меньший текстовый объём. Он не зависит от языка программирования и может найти примерение во многих областях: валидация форм POST-запросов, JSON REST API, проверка пакетов при обмене данными через сокеты, валидация документов в документо-ориентированных БД и т. д. Основным преимуществом использования JSON-Schema является стандартизация и, как следствие, упрощение поддержки и улучшение интеграции ПО.
ссылка на оригинал статьи http://habrahabr.ru/post/158927/
Добавить комментарий