JSONPathテスター
JSONPath式を書けば、マッチした結果がその場で見えます。RFC 9535準拠。
貼り付けたものがブラウザの外に出ることはありません。 connect-src の許可リストにより、これは約束ではなくブラウザによる保証になっています。 自分で確かめる
JSONPath式を書けば、自分のドキュメントに対するすべてのマッチが、それぞれの正規化パスとともに表示されます。狙ったものを選べるようになるまで、編集して実行を繰り返してください。
ここでの構文は、2024年2月に公開されたIETFのProposed StandardであるRFC 9535に従います。これは聞こえる以上に重要です。というのも、その前の17年間、仕様というものが存在しなかったからです。
なぜ方言を明示する必要があるのか
JSONPathは2007年のStefan Goessnerのブログ記事として始まりました。広く実装された一方で仕様化されることはなく、実装は面白い部分のほぼすべてで食い違いました。$..がルートを含むのか、負のインデックスは何を意味するのか、$[0,1]は和集合になるのか、キーが存在しないときフィルターはどう振る舞うのか、スライスのステップが0だとどうなるのか。ある比較プロジェクトは、この種の不一致を数百件記録しています。
RFC 9535がそれに決着をつけました。どの方言を実装しているか言わないテスターは、問いを示さずに答えだけを渡しているようなものです。ですのでこのツールは明示します。RFC 9535、ただし以下の除外事項つきです。
ここでサポートしている構文
- $
- ドキュメントのルート。すべての式はここから始まります。
- .name と ['name']
- 名前付きメンバー。空白や記号を含む名前には角括弧形式を使ってください。
- .* と [*]
- オブジェクトのすべてのメンバー、または配列のすべての要素。
- ..
- 子孫セグメント。この階層と、その下のすべての階層を探索します。
- [0] と [-1]
- 配列のインデックス。負の値は末尾から数えます。
- [1:5]、[::2]、[::-1]
- スライス。RFC 9535の意味論に従います。ステップが負の場合は逆順にたどります。
- [0, 2, 'name']
- 1つのセグメントに複数のセレクター。結果の和集合になります。
- [?<式>]
- フィルター。内部では@が現在の要素、$がドキュメントのルートです。== != < <= > >= で比較し、&& || と ! で組み合わせられます。
- length() count() match() search() value()
- RFC 9535が定義する関数拡張です。match()は文字列全体にアンカーし、search()はしません。
意図的にサポートしていないもの
[(...)]という形のスクリプト式は仕様化されたことがなく、RFC 9535で削除されました。親要素を指す^演算子は一部の実装が加えた拡張であり、RFCには含まれていません。また擬似プロパティとしての@.lengthは、現在のlength(@)にあたるRFC以前の書き方です。これらを貼り付けた場合、黙って何も返さずに、そうであることを明示します。
JSONPath、JMESPath、jq、JSON Pointer
JSONドキュメントの一部を指し示す4つの方法で、それぞれ役割が違います。
- JSONPath
- ノードの集合を選択します。深さを問わずパターンに合うものをすべて取りたいときに最適です。現在はRFC 9535として標準化されています。
- JMESPath
- 選択だけでなく変換もします。プロジェクション、マルチセレクトハッシュ、パイプ式で出力の形を作り替えられます。AWS CLIで使われており、最初から本物の仕様があります。
- jq
- クエリ構文が付いた完全なプログラミング言語です。処理が選択というよりプログラミングに近いときに使ってください。
- JSON Pointer(RFC 6901)
- ワイルドカードもフィルターもなく、ちょうど1か所を指し示します。意図的に単純で、だからこそJSON PatchもJSON Schemaもこれを使っています。エスケープは2つ、チルダが~0、スラッシュが~1です。
How to do this in code
同じクエリをコードで実行する方法。
py Python
# jsonpath-ng is the most complete Python implementation
from jsonpath_ng.ext import parse
expr = parse('$.store.book[?(@.price < 10)].title')
titles = [m.value for m in expr.find(data)]
# JMESPath, if you prefer a specified language with projections
import jmespath
titles = jmespath.search('store.book[?price < `10`].title', data) js JavaScript
import { JSONPath } from 'jsonpath-plus';
const titles = JSONPath({
path: '$.store.book[?(@.price < 10)].title',
json: data,
});
// Get the normalised paths rather than the values
const paths = JSONPath({ path: '$..author', json: data, resultType: 'path' }); sh jq
jqには「子孫かつフィルター」に相当する演算子がないため、2つの部分を分けて書きます。
# The jq equivalent of a filtered descendant search
jq '.store.book[] | select(.price < 10) | .title' data.json
# Every value at any depth under a key
jq '.. | .author? // empty' data.json java Java
Jayway JsonPathはRFC 9535より前からあり、いくつかの点で、とくにキーが存在しない場合のフィルターの扱いで挙動が異なります。
import com.jayway.jsonpath.JsonPath;
List<String> titles = JsonPath.read(json, "$.store.book[?(@.price < 10)].title"); よくある質問
- 式が何も返さないのはなぜですか?
- たいてい原因は3つのどれかです。空白やハイフンを含むため角括弧とクォートが必要な名前、クォートしていない文字列との比較(@.type == book ではなく @.type == 'book' と書きます)、あるいはドキュメントがオブジェクトの場所で配列を前提にしているパスです。式そのものが不正な場合は位置つきのパースエラーを報告し、式が妥当で何にも一致しなかったときにだけ結果が空になります。
- 正規化パスとは何ですか?
- RFC 9535は、マッチした位置の正規表記を定義しています。角括弧とクォートで囲んだ名前と数値インデックスを使い、$['store']['book'][0]['title']のように書きます。ここでのマッチにはすべてこれが付くので、実装をまたいで結果を比較できます。
- $..* は $.. と同じですか?
- 違います。これもRFCが決着をつけた食い違いのひとつです。$..*はルートを除くすべての子孫ノードを選択します。一方、単独の$..はそもそも完全な式ではありません。