JSONスキーマと、C ++でJSONドキュメントを検証するためのその使用

この記事では、JSONスキーマ標準と、 valijsonライブラリーを使用してC ++の特定の形式への準拠を確認するためのその使用について説明します。



ちょっとした歴史


そもそも、JSONによるXMLの普及につながった原因と、これが悪いことを思い出してみましょう。 XMLは元々 ドキュメントマークアップの メタ言語として作成されたため、パーサーとドキュメントバリデーターに統一されたコードを使用できます。 デジタル企業情報システムの急速な実装の時代にさかのぼるこの種の最初の標準であるXMLは、データシリアル化および相互作用プロトコルの無数の標準の基礎となりました。 構造化データの保存と送信。 これは、主にドキュメントをマークアップするために作成されました。



委員会によって開発されているため、XML標準は多くの拡張機能によって補完されており、特に名前の競合を回避し 、XMLドキュメントで複雑なクエリを実行できます。 そして、最も重要なことは、結果として生じるタグの積み上げが誰にも完全に読めないことが判明したため、 XML Schema標準が開発され、広く実装されました。



一方、新生インタラクティブWebテクノロジーの影響下で、ますます多くの開発者がJavaScript言語に慣れ始め、構造化されたオブジェクトをテキスト形式で表すために何百ページものXML仕様を勉強する必要がないことに気付き始めました。 そして、Douglas Crockfordがオブジェクトのシリアル化のためにJavaScriptのサブセットを標準化することを提案したとき(マークアップドキュメントではありません!)言語に関係なく、このアイデアはコミュニティによってサポートされました。 現在、JSONは、すべての一般的なプログラミングテクノロジーでサポートされている2つの(XMLとともに)言語の1つです。 同じYAMLは、その複雑さ(つまり、可能性の幅)のためにJSONをより便利で人間が読めるように設計されています。



そのため、データを表すためにJSONを大規模に使用し始めた開発者は、各言語でドキュメントのコンテンツを毎回手動で確認し、検証ロジックを再作成する必要に直面しています。 XMLスキーマに精通している人々は、怒りを覚えるしかありませんでした。 そして徐々に、同様のJSONスキーマ標準が形成され、 http://json-schema.org/に存在します。



JSONスキーマ


数字で構成されるキーを使用して、空間(-1、1)x(-1、1)x(-1、1)の2Dまたは3Dの幾何学的な点の辞書を定義する、シンプルですが、示唆的なスキームの例を考えてみましょう。



{ "type": "object", "patternProperties": { "^[0-9]+$": { "type": "object", "properties": { "value": { "type": "number", "minimum": 0 } "x": { "$ref": "#/definitions/point_coord" }, "y": { "$ref": "#/definitions/point_coord" }, "z": { "$ref": "#/definitions/point_coord" } }, "required": ["value", "x", "y"] } } "additionalProperties": false, "definitions": { "point_coord": { "type": "number", "minimum": -1, "maximum": 1 } } }
      
      





Crockfordの迷惑な引用を許す場合、このドキュメントから、キーが数字(正規表現を参照)で構成されているオブジェクト(辞書)を処理することに同意することは明らかです。その値は、フィールドx、y、value、フィールドz、値は非負の数値であり、x、y、zはすべて、-1から+1までの数値に対応する同一のpoint_coord型を持っています。 JSONスキーマが他の機能を提供していないと仮定しても(これは真実とはほど遠い)、これは多くのユースケースに十分なはずです。



しかし、これはあなたの言語/プラットフォームにバリデーターが実装されている場合です。 XMLの場合、このような問題はほとんど発生しません。



http://json-schema.org/サイトで、検証ソフトウェアのリストを見つけることができます。 そしてこの場所では、JSON-Schema(およびそのWebサイト)の未熟さが感じられます。 C ++の場合、1つ(一見興味深い) libvariantライブラリが指定されます 。これは、パートタイムでのみ検証され、悪意のあるLGPLライセンス(さようなら、iOS)でリリースされます。 Cの場合オプション1つあり、LGPLの下にもあります



ただし、許容可能な解決策が存在し、 valijsonと呼ばれます 。 このライブラリには、必要なものすべて(回路の検証とBSDライセンス)があり、JSONパーサーからは独立しています。 Valijsonを使用すると、アダプター(jsoncpp、json11、rapidjson、picojson、boost :: property_treeにバンドルされたアダプター)を使用してjsonパーサーを使用できるため、新しいjsonライブラリに切り替える(または別のライブラリをドラッグする)必要がありません。 さらに、ヘッダーファイルのみで構成され(ヘッダーのみ)、コンパイルは不要です。 明らかなマイナスは1つだけであり、誰にとってもそうではありません。ブーストへの依存です。 この欠点からでも救出の希望ありますが



JSONスキームをコンパイルし、このドキュメントを検証するドキュメントの例を見てみましょう。



回路図の例


特定の縞模様の色が与えられたいくつかの縞模様のオブジェクトのテーブルがあると仮定します(白黒に対応する0と1のシーケンスの形式で)。



 { "0inv": { "width": 0.11, "stripe_length": 0.15, "code": "101101101110" }, "0": { "width": 0.05, "stripe_length": 0.11, "code": "010010010001" }, "3": { "width": 0.05, "stripe_length": 0.11, "code": "010010110001" }, ... }
      
      





ここに数字キー付きの辞書があり、それにサフィックス「inv」を割り当てることができます(反転バーコード用)。 ディクショナリ内のすべての値はオブジェクトであり、フィールド「width」、「stripe_length」(厳密に正の数)、および「code」(ゼロのストリングと長さ12のユニット)が必要です。



最上位フィールドの名前の形式の制限を示す図の作成を開始します。



 { "comment": "Schema for the striped object specification file", "type": "object", "patternProperties": { "^[0-9]+(inv)?$": { } }, "additionalProperties": false }
      
      





ここではpatternPropertiesコンストラクトを使用しました。これにより、キーが正規表現を満たす値を許可または指定します。 また、未指定のキーは許可されないことを示しました(additionalProperties = false)。 additionalPropertiesを使用すると、明示的に指定されていないフィールドを許可または禁止できるだけでなく、たとえば次のように値として型指定子を指定することで、値に制限を課すこともできます。



 { "additionalProperties": { "type": "string", "pattern": "^Comment: .*$" } }
      
      





次に、ディクショナリ内の各オブジェクトの値のタイプを説明します。



 { "type": "object", "properties": { "width": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "stripe_length": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "code": { "type": "string", "pattern": "^[01]{12}$" } }, "required": ["width", "stripe_length", "code"] }
      
      





ここでは、許可されたフィールド(プロパティ)を明示的にリストし、追加のプロパティを禁止(デフォルト)せずに、それらの存在を要求します(必須)。 数値プロパティは厳密に正であり、文字列コードは正規表現と一致する必要があります。



原則として、個々のオブジェクトのタイプの説明を上記のテーブルレイアウトに挿入するだけです。 しかし、これを行う前に、「幅」フィールドと「stripe_length」フィールドの仕様を複製したことに注意してください。 例の元となる実際のコードには、このようなフィールドがさらにあります。したがって、この型を一度定義してから、どこからでも参照すると便利です。 このために、リンクメカニズム($ ref)があります。 最終図の定義セクションに注意してください。



 { "comment": "Schema for the striped object specification file", "type": "object", "patternProperties": { "^[0-9]+(inv)?$": { "type": "object", "properties": { "width": { "$ref": "#/definitions/positive_number" }, "stripe_length": { "$ref": "#/definitions/positive_number" }, "code": { "type": "string", "pattern": "^[01]{12}$" } }, "required": ["width", "stripe_length", "code"] } }, "additionalProperties": false, "definitions": { "positive_number": { "type": "number", "minimum": 0, "exclusiveMinimum": true } } }
      
      





ファイルに保存し、バリデーターの作成に進みます。



Valijsonアプリケーション


jsonパーサーとしてjsoncppを使用します。 ファイルからjsonドキュメントをロードする通常の機能があります。



 #include <json-cpp/json.h> Json::Value load_document(std::string const& filename) { Json::Value root; Json::Reader reader; std::ifstream ifs(filename, std::ifstream::binary); if (!reader.parse(ifs, root, false)) throw std::runtime_error("Unable to parse " + filename + ": " + reader.getFormatedErrorMessages()); return root; }
      
      





すべての検証エラーの場所を示す最小限の検証関数は、次のようになります。



 #include <valijson/adapters/jsoncpp_adapter.hpp> #include <valijson/schema.hpp> #include <valijson/schema_parser.hpp> #include <valijson/validation_results.hpp> #include <valijson/validator.hpp> void validate_json(Json::Value const& root, Json::Value const& schema_js) { using valijson::Schema; using valijson::SchemaParser; using valijson::Validator; using valijson::ValidationResults; using valijson::adapters::JsonCppAdapter; JsonCppAdapter doc(root); JsonCppAdapter schema_doc(schema_js); SchemaParser parser(SchemaParser::kDraft4); Schema schema; parser.populateSchema(schema_doc, schema); Validator validator(schema); validator.setStrict(false); ValidationResults results; if (!validator.validate(doc, &results)) { std::stringstream err_oss; err_oss << "Validation failed." << std::endl; ValidationResults::Error error; int error_num = 1; while (results.popError(error)) { std::string context; std::vector<std::string>::iterator itr = error.context.begin(); for (; itr != error.context.end(); itr++) context += *itr; err_oss << "Error #" << error_num << std::endl << " context: " << context << std::endl << " desc: " << error.description << std::endl; ++error_num; } throw std::runtime_error(err_oss.str()); } }
      
      





この例では、jsoncppは#include <json-cpp/json.h>



として接続されていますが、valijsonの現在のバージョンのvalijson valijson/adapters/jsoncpp_adapter.hpp



jsoncpp_adapter.hppはjsoncppが#include <json/json.h>



として接続されていることを前提としてい#include <json/json.h>



。 コンパイラがjson/json.h



見つけなくても、驚かないで、 valijson/adapters/jsoncpp_adapter.hpp







これで、ドキュメントをアップロードして検証できます。



 Json::Value const doc = load_document("/path/to/document.json"); Json::Value const schema = load_document("/path/to/schema.json"); try { validate_json(doc, schema); ... return 0; } catch (std::exception const& e) { std::cerr << "Exception: " << e.what() << std::endl; return 1; }
      
      





以上で、jsonドキュメントを検証する方法を学びました。 ただし、ここで、回路の保存場所を検討する必要があることに注意してください! 実際、ドキュメントが毎回変更され、たとえばWeb要求またはコマンドライン引数から取得される場合、スキームは変更されず、アプリケーションとともに配信する必要があります。 また、静的リソースをロードするためのメカニズムが開発されていない小規模なプログラムの場合、導入する必要があるため、スキームによる検証の実装に対する大きな障壁となります。 スキームを変更するには、ドキュメントを処理するコードを変更する必要があるため、スキームをプログラムでコンパイルすることは素晴らしいことです。



これは、C ++ 11を自由に使用できる場合に可能であり、非常に便利です。 ソリューションは原始的ですが、うまく機能します。スキーマで文字列定数を定義するだけです。 そして、文字列内の引用符を気にしないために、 生の文字列リテラルを使用します



 //   R"(raw string)" static std::string const MY_SCHEMA = R"({ "comment": "Schema for pole json specification", "type": "object", "patternProperties": { "^[0-9]+(inv)?$": { ... ... } } ... })"; //  json   Json::Value json_from_string(std::string const& str); { Json::Reader reader; std::stringstream schema_stream(str); Json::Value doc; if (!reader.parse(schema_stream, doc, false)) throw std::runtime_error("Unable to parse the embedded schema: " + reader.getFormatedErrorMessages()); return doc; } //    doc (validate_json  ) validate_json(doc, json_from_string(MY_SCHEMA));
      
      





したがって、jsonドキュメントを検証するための便利なクロスプラットフォームクロス言語メカニズムがあり、C ++で使用すると、外部ライブラリを不便なライセンスにリンクしたり、静的リソースへのパスをいじったりする必要がありません。 これは本当に多くのエネルギーを節約でき、重要なことに、オブジェクトを表現するためのフォーマットとしてXMLを永久に殺すのに役立ちます。これは人とマシンの両方にとって不便だからです。



All Articles