JSONPath テスター

RFC 9535 の JSONPath クエリを JSON に対してテスト。セレクタ・フィルタ・5 つの関数、そして各一致の正規化パスに対応。すべてブラウザ内で動作します。

JSONPath クエリ
JSON
一致: 2
  • パス$['store']['book'][0]['title']
    "Sayings of the Century"
  • パス$['store']['book'][2]['title']
    "Moby Dick"

ついに標準ができたクエリ言語

JSONPath は JSON にとって、XPath が XML にとってのものと同じです。文書の一部を指し示す小さな言語です。$.store.book[0].title のような式を書くと、一致するノードを選択します。この考えは 2007 年の Stefan Goessner のブログ記事から来ており、十七年間その記事が唯一の参照でした — つまり、各ライブラリが隙間を別々に埋めたということです。単独の $.. は何を選びますか? $[1,2] は順序どおりに返ると保証されますか? フィルタは存在しない値をどう比較しますか? 三つの JSONPath ライブラリに尋ねれば、三つの答えが返ってくるかもしれません。

2024 年に公開された RFC 9535 が、ついにそのすべてを確定させました。このテスターは俗説ではなくその標準を狙います。RFC の言うとおりにクエリを実行し、各一致をその正確な位置とともに表示し、そして — 重要なことに — 古い方言では有効でも RFC 9535 では無効なクエリを拒否し、別のツールなら違うことをする非標準の動作を黙って行う代わりに、問題の場所を教えます。

セグメントとセレクタ

クエリはルート識別子 $ にセグメントの並びが続いたもので、各セグメントは受け取ったノードに一つ以上のセレクタを適用します。セレクタは五つあります。

  • 名前 — $.store または $["store"] はメンバーの値を選びます。ドット形式は略記で、角括弧と引用符の形式は、空白や句読点を含むものも含めどんなキーにも使えます。
  • ワイルドカード — * はオブジェクトのすべてのメンバー、または配列のすべての要素を選びます。
  • インデックス — [0] は配列要素を選び、負のインデックスは末尾から数えるので [-1] は最後です。
  • スライス — [start:end:step] は範囲を選びます。Python とまったく同じで、[1:3] は要素 1 と 2、[::-1] は反転、[::2] は一つおきに取ります。
  • フィルタ — [?<式>] は式が真である要素またはメンバーだけを残します(後述)。

角括弧は一度に複数のセレクタを持てます。[0, 2, "title"] は一つのセグメントで三つを選びます。そしてセグメントは子セグメント(単一のドットまたは角括弧)でも、子孫セグメント(..)でもよく、後者はノードとそのすべての子孫を検索します — $..author は文書のどこにある author も見つけます。

フィルタと、無を比較する四つの方法

フィルタセレクタは各要素を論理式で検査します。式の中で @ は現在の要素を、$ は文書全体を指します。式は値を比較でき(==, !=, <, <=, >, >=)、&& と || と ! で検査を組み合わせられ、単に存在を検査できます。$..book[[email protected]] は ISBN を持つ本を残します。なぜなら @.isbn はキーが存在するときにのみノードを選ぶからです。

微妙なのは、存在しないものを比較することです。比較の左または右のクエリは値か無を生みます。RFC はこれを正確に定義します。無は無に等しく、無はいかなる実在の値にも等しくなく、無に対するあらゆる順序検査(<, >)は単に偽です。したがって @.price < 10 は price を持たない要素を、エラーにする代わりに黙ってスキップします — 通常望むことであり、常に標準が言うことです。

五つの関数

RFC 9535 はフィルタ内で呼べる五つの関数を追加します。

  • length() — 文字列(文字数で数える)、配列、またはオブジェクトの長さ。
  • count() — クエリが選ぶノードの数。基数でフィルタできます。[?count(@.chapters) > 3]。
  • value() — クエリが選ぶ唯一の値、または零個か複数を選ぶ場合は無。
  • match() — 文字列が正規表現に完全に一致するか。
  • search() — 正規表現が文字列のどこかに見つかるか。

標準はこれらの使い方に厳格で、このテスターは解析時にそれを強制します。length() はちょうど一つの値を取るので、length(@.*) — 多くのノードを選びうるクエリ — は構文エラーであり、黙って誤動作するクエリではありません。同様に match() は真か偽を返すので、match(@.a, "x") == true と書くと拒否されます。論理結果は比較するものではないからです。

正規表現は I-Regexp です

match() と search() は JavaScript の正規表現を使いません。I-Regexp(RFC 9485)を使います。これはすべての言語で同じように振る舞うよう設計された、小さく可搬なサブセットです。大部分は予想どおり — 文字クラス、量指定子、選択、大文字を表す \p{Lu} のような Unicode プロパティ。唯一の罠はドットです。I-Regexp では . は復帰(CR)または改行(LF)を除くあらゆる文字に一致します。つまり、JavaScript のドットが除外する Unicode の行区切り U+2028 と U+2029 には一致します。このテスターは I-Regexp を忠実にコンパイルするので、パターンは適合サーバーが評価するのと同じようにここで振る舞います。

二つの関数の違いはアンカーだけです。match は文字列全体が一致することを要求し、search はその中のどこかにパターンを探します。match(@, "a.*") は "abc" を受け入れ、search(@, "b") は b を含む任意の文字列を受け入れます。

正規化パスと、ブラウザでの実行

一致ごとに、このテスターは Normalized Path を表示します — RFC が定める正準的な位置で、角括弧と引用符の形式で書かれます。$['store']['book'][0]['author']。多くのノードを選びうるクエリと違い、正規化パスはちょうど一つを指し、名前とインデックスのセレクタだけと固定の引用スタイルを用います。それは「この一致はどこから来たか」への答えであり、ワイルドカードの結果を具体的な位置の集合に戻せるものです。ほとんどのテスターは値を表示してパスの算出はあなたに任せますが、これは両方を表示します。

エンジン全体があなたのブラウザ内で動きます — JSON は解析され、クエリはあなたの端末で評価され、貼り付けた内容はアップロードも保存も記録もされません。公式の JSONPath Compliance Test Suite で検証されているので、その答えは一つのライブラリの解釈ではなく標準と一致します。

よくある質問

これはどの JSONPath 方言を使いますか?
RFC 9535、2024 年の IETF 標準で、公式の JSONPath Compliance Test Suite で検証されています。RFC から逸れるところで古い Goessner の慣習を意図的に受け入れません。そのようなクエリは、修正できるよう問題の位置とともに拒否されます。
別のツールでは動くのに、なぜ私のクエリは拒否されたのですか?
そのツールが標準以前の Goessner の慣習に従っているからです。それは RFC 9535 と数か所で異なります — 単独の $..、[(@.length-1)] のようなスクリプト式、先頭ゼロのインデックス、特殊文字を含む引用符なしの名前はすべて非標準です。RFC はそれらを明確に定義された同等物に置き換えており、このテスターは RFC を守ります。
正規化パスとは何ですか?
単一ノードの正準的な位置で、RFC が定める角括弧と引用符の形式で書かれます。例えば $['store']['book'][0]['author']。クエリは多くのノードに一致しえますが、各一致にはちょうど一つの正規化パスがあり、だからテスターはそれをすべての値の隣に表示します。
フィルタは欠けた値をどう扱いますか?
無として、RFC が定める規則で扱います。無は無に等しく、無はいかなる実在の値にも等しくなく、無を含むあらゆる順序比較(<, >)は偽です。したがって @.price < 10 は price のない要素を、エラーを投げる代わりに単にスキップします。
match() と search() は JavaScript の正規表現を使いますか?
いいえ。I-Regexp(RFC 9485)という可搬なサブセットを使います。主な実用上の違いはドットで、復帰と改行を除くすべてに一致します — JavaScript のドットが除外する U+2028 と U+2029 を含みます。このテスターは I-Regexp を忠実にコンパイルするので、結果は適合実装と一致します。
match と search の違いは何ですか?
アンカーです。match() は文字列全体がパターンに一致することを、あたかもアンカーで囲まれているかのように要求します。search() はパターンが文字列のどこかに見つかれば成功します。他はすべて同じです。
私の JSON はサーバーに送信されますか?
いいえ。文書は解析され、クエリはすべてブラウザ内で評価され、貼り付けた内容はアップロードも記録もされません。本物の API レスポンスや設定ファイルに対して安全にテストできます。