多くのサービスとアプリケーション(特にWebサービス)はツリーデータを受け入れます。 たとえば、このフォームには、JSON-PRC、JSON-REST、PHP-GET / POSTを介して受信したデータがあります。 当然、タスクはその構造を検証することです。 この問題を解決するための多くのオプションがあります。コントローラーにifを積み上げてから、さまざまな構成の検証を実装するクラスで終わります。 ほとんどの場合、この問題を解決するには、特定の規格で記述されたデータスキームで動作する再帰的なバリデーターが必要です。 これらの標準の1つはJSON-Schemaです。詳しく見てみましょう。
JSON-schemaは、 XML-Schemaに基づいて開発されたJSON形式でデータ構造を記述するための標準です。ドラフトはここにあります (以下の説明はバージョン03に対応します)。 この標準で記述されているスキーマには、MIME「application / schema + json」があります。 標準は、数値、文字列、配列、およびキー値構造(プログラミング言語に応じて、オブジェクト、辞書、ハッシュテーブル、連想配列、またはマップと呼ぶことができる)で構成されるデータ構造を検証および文書化するときに便利です。 「オブジェクト」または「オブジェクト」という名前が使用されます)。 現時点では、さまざまなプラットフォームと言語、特にjavascript、php、ruby、python、javaの完全および部分的な実装があります。
スキーム
スキーマは、JSON形式のデータを記述するために設計されたJSONオブジェクトです。 このオブジェクトのプロパティはオプションであり、それぞれが特定の検証ルール(以降-ルール)の指示です。 まず第一に、スキームはデータ型を制限できます(ルールはtypeまたはdisallow、文字列または配列のいずれかです):
- 文字列(文字列)
- 数値(すべての実数を含む数値)
- 整数(整数、数値のサブセット)
- ブール値(trueまたはfalse)
- オブジェクト(一部の言語では、連想配列、ハッシュ、ハッシュテーブル、マップ、またはディクショナリと呼ばれるオブジェクト)
- 配列(配列)
- null(「データなし」または「不明」。nullのみが可能)
- any(nullを含む任意のタイプ)
さらに、チェックされるデータのタイプに応じて、追加のルールが適用されます。 たとえば、チェック対象のデータが数値の場合、最小値、最大値、divisibleByを適用できます。 チェックされるデータが配列の場合、ルールが有効になります:minItems、maxItems、uniqueItems、items。 チェック対象のデータが文字列の場合、適用:pattern、minLength、maxLength。 オブジェクトがチェックされている場合、ルールが考慮されます:properties、patternProperties、additionalProperties。
タイプ固有のルールに加えて、id、title、description、$ schemaなどの説明的なルールだけでなく、必須や形式などの追加の一般的なルールもあります。 仕様では、日付時刻(ISO 8601)、日付、時刻、utc-ミリ秒、正規表現、色(W3C.CR-CSS21-20070719)、スタイル(W3C.CR-CSS21-20070719)、電話、 uri、email、ip-address(V4)、ipv6、host-name。現在の実装で定義およびサポートされている場合は、さらに確認できます。 仕様でこれらと他の規則をより詳細に見つけることができます。
スキーマはJSONオブジェクトであるため、対応するスキーマで確認することもできます。 現在のスキーマが対応するスキーマは、$ schema属性に書き込まれます。 これを使用して、スキームの作成に使用されたドラフトのバージョンを判別できます。 ここでこれらのスキームを見つけます 。
JSON-Schemaの最も強力で魅力的な機能の1つは、スキームから他のスキームを参照したり、( 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, }
さて、このスキームを使用して、製品の名前に一致する任意の文字列を記述または検証できます。 さらに、製品の価格は負ではなく、タイプ(「電話」または「ノートブック」)、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 } } }
このスキームは、以前のスキームへのリンクと、必要なルールを含むその拡張を使用します。 以前のスキームではこれを行うことができません。名前がどこかにオプションであり、すべてのルールが適用されるためです。
性能
もちろん、JSONスキーマに基づくバリデーターのパフォーマンスは、バリデーターの実装とルールの完全なサポートに依存します。 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):
名前の検証:180ms
製品検証:743ms
おわりに
JSON-Schemaは、データ構造を文書化し、アプリケーションで自動外部データ検証を構成するための非常に便利なツールです。 XMLスキーマよりもシンプルで読みやすいように見えますが、使用するテキストスペースは小さくなります。 プログラミング言語に依存せず、多くの分野で例を見つけることができます:POSTリクエストフォームの検証、JSON REST API、ソケットを介したデータ交換時のパケットのチェック、ドキュメント指向データベースのドキュメントの検証など。JSONスキーマを使用する主な利点標準化であり、結果として、サポートの簡素化とソフトウェア統合の改善です。