(この機能はv1.1.0で公開されました)
JSON Pointerは、JSONドキュメント(DOM)内の値を選択するための標準化された方法(RFC6901)です。XMLドキュメントのXPathに似ていますが、JSON Pointerははるかに単純で、一つのJSON Pointerは一つの値だけを指します。
RapidJSONのJSON Pointer実装を使うと、DOMの一部の操作を簡単にできます。
JSON Pointer
JSON Pointerは、各トークンの先頭に/を付けた、0個以上のトークンのリストです。各トークンは文字列か数値です。たとえば、次のJSONを考えます。
以下のJSON Pointerは、このJSONに対して次の値を指します。
- "/foo" → [ "bar", "baz" ]
- "/foo/0" → "bar"
- "/foo/1" → "baz"
- "/pi" → 3.1416
空のJSON Pointer ""(トークン数0)は、JSON全体を指すことに注意してください。
基本的な使い方
次のコード例を見れば、使い方が分かります。
ヘルパー関数
オブジェクト指向の呼び出し方は直感的でない場合があるため、RapidJSONには、メンバー関数を自由関数で包んだヘルパー関数もあります。
次の例は、前の例とまったく同じ処理を行います。
比較のため、呼び出し方を並べます。
- Pointer(source).<Method>(root, ...)
- <Method>ValueByPointer(root, Pointer(source), ...)
- <Method>ValueByPointer(root, source, ...)
ポインターの解決
Pointer::Get()やGetValueByPointer()は、DOMを変更しません。トークンに一致する値がDOM内になければ、nullptrを返します。これを使って、値が存在するかを確認できます。
数値のトークンは、配列の添字にもメンバー名にもなります。解決処理は、値の型に応じて値を照合します。
- "/0" → 123
- "/1/0" → 456
トークン"0"は、最初のポインターではメンバー名、2番目のポインターでは配列の添字として扱われます。
Create()、GetWithDefault()、Set()、Swap()などのほかの関数は、DOMを変更します。これらの関数は常に成功します。親の値が存在しなければ作成し、親の値がトークンに一致しなければ、その型を強制的に変更します。型を変更すると、そのDOMの部分木は完全に削除されます。
上のJSONをdへ解析すると、次のようになります。
マイナス記号トークンの解決
また、RFC6901は、配列の末尾の次の要素を表す特別なトークン-(マイナス記号一つ)を定義しています。Get()は、このトークンをメンバー名「"-"」としてだけ扱います。一方、ほかの関数は配列に対して解決でき、配列のValue::PushBack()を呼ぶのと同じになります。
DocumentとValueに対する解決
p.Get(root)やGetValueByPointer(root, p)を使う場合、rootは(constの)Value&です。つまり、DOMの部分木でもかまいません。
ほかの関数には、2種類のシグネチャがあります。一方は引数にDocument& documentを使い、もう一方はValue& rootを使います。前者は値の作成にdocument.GetAllocator()を使います。後者は、DOMの関数と同様に、利用者がアロケーターを渡す必要があります。
上の例はすべて、第1引数がDocument&なので、アロケーター引数は不要です。ただし、ポインターを部分木に対して解決する場合は、次の例のようにアロケーターを渡す必要があります。
Erase()やEraseValueByPointer()は、アロケーターを必要としません。値を正常に削除すると、trueを返します。
エラー処理
Pointerは、コンストラクターで入力文字列を解析します。解析エラーがあると、Pointer::IsValid()はfalseを返します。Pointer::GetParseErrorCode()とGetParseErrorOffset()で、エラー情報を取得できます。
すべての解決関数は、有効なポインターを前提とすることに注意してください。無効なポインターで解決すると、アサーションに失敗します。
URIフラグメント表現
ここまで使ってきたJSON Pointerの文字列表現に加え、RFC6901はURIフラグメント表現も定義しています。URIフラグメントは、RFC3986「Uniform Resource Identifier (URI): Generic Syntax」で規定されています。
主な違いは、URIフラグメントの先頭に必ず#(番号記号)が付き、一部の文字はUTF-8のバイト列をパーセントエンコードして表すことです。次の表は、各表現のC/C++文字列リテラルを示します。
| 文字列表現 | URIフラグメント表現 | ポインターのトークン(UTF-8) |
|---|---|---|
| "/foo/0" | "#/foo/0" | {"foo", 0} |
| "/a~1b" | "#/a~1b" | {"a/b"} |
| "/m~0n" | "#/m~0n" | {"m~n"} |
| "/ " | "#/%20" | {" "} |
| "/\0" | "#/%00" | {"\0"} |
| "/€" | "#/%E2%82%AC" | {"€"} |
RapidJSONはURIフラグメント表現を完全にサポートし、解析時に番号記号を自動検出します。
文字列化
Pointerを文字列やほかの出力ストリームへ文字列化することもできます。方法は次のとおりです。
StringifyUriFragment()で、URIフラグメント表現へ文字列化することもできます。
利用者が指定するトークン
ポインターを何度も解決する場合は、一度だけ構築し、異なるDOMや異なる時点で使うようにします。これにより、Pointerを何度も構築する時間とメモリー確保を減らせます。
さらに、トークン配列を直接作ることで、解析処理と動的なメモリー確保を完全になくせます。
これは、メモリーに制約のあるシステムで役立つ場合があります。