N2
NanToo
JSONPath はなぜツールごとに書き方が違うのか ― RFC 9535 と jq / JavaScript パスの実務整理
DDEVELOPER
開発11 分で読める

JSONPath はなぜツールごとに書き方が違うのか ― RFC 9535 と jq / JavaScript パスの実務整理

JSON の中から items[0].name のような値を取り出したい場面はよくあります。ところが、ツールによって $.items[0].name.items[0].nameitems[0].name["items",0,"name"] のように表記が変わり、同じものに見えてもそのまま貼り替えると動かないことがあります。

この記事では、RFC 9535 で標準化された JSONPath を中心に、jq のフィルタ、JavaScript のプロパティアクセス、lodash の path との違いを整理します。ポイントは、これらを「JSON の住所」として似たものに見すぎず、評価するエンジンが違う別言語として扱うことです。

#JSONPath#JSON#jq#RFC 9535#JavaScript#API

JSONPath は JSON の中のノードを選ぶクエリ言語

JSONPath は、JSON 文書の中から値を選択するためのクエリ表記です。たとえば次の JSON から最初の商品名を取りたいなら、JSONPath では $.items[0].name のように書きます。

{
  "items": [
    { "name": "USB-C cable", "price": 1200 },
    { "name": "charger", "price": 2980 }
  ]
}

先頭の $ はルートノード、.items はオブジェクトのメンバー、[0] は配列の 0 番目、.name はその中の名前です。ファイルパスのように見えますが、実際には JSON の木構造に対する問い合わせです。

RFC 9535 で標準化された範囲

JSONPath は長いあいだ実装ごとの差が大きい記法でしたが、2024 年 2 月に RFC 9535 として標準化されました。RFC 9535 は、JSONPath を「JSON 値の中から値を選択し抽出する文字列構文」として定義しています。

表記意味
$ルートノード$
.name名前による子の選択$.items
['name']引用符つき名前選択$['user.name']
[0]配列インデックス$.items[0]
[*]子要素のワイルドカード$.items[*].name
..name子孫方向の探索$..price
[?expr]条件に合う要素の選択$.items[?@.price < 2000]

RFC 9535 の重要な点は、単に書き方を並べたことではなく、結果を「ノードリスト」として扱うこと、フィルタ式の現在ノードを @ で表すこと、配列スライスやワイルドカードの意味を定義したことです。

$ と @ を混同しない

JSONPath で最初に混乱しやすいのが $@ です。$ は常に文書全体のルートを表します。一方、@ はフィルタの中で評価中の現在ノードを表します。

$.items[?@.price < 2000].name

この例では、$.items で配列まで進み、[?@.price < 2000] で各要素を 1 つずつ見ます。フィルタ内の @.price は「いま見ている商品オブジェクトの price」です。ここで $.price と書くと、ルート直下の price を見に行く別の意味になります。

jq の .items[0].name は JSONPath ではない

jq でも .items[0].name のような表記を使いますが、これは JSONPath ではなく jq のフィルタです。jq では . が入力そのものを表す identity filter で、.foo はオブジェクトのキー foo を取り出すフィルタです。

似ているため混同しやすいのですが、jq は値の抽出だけでなく、変換、計算、パイプ、配列生成、複数出力などを持つプログラミング言語に近い道具です。JSONPath は基本的に「どのノードを選ぶか」を表すクエリ言語です。

目的JSONPathjq
最初の商品名$.items[0].name.items[0].name
全商品名$.items[*].name.items[].name
2000円未満$.items[?@.price < 2000].items[] | select(.price < 2000)

jq の .items[].name は、各要素を個別の出力として流すイメージです。JSONPath の $.items[*].name は、該当ノードの集合を選ぶイメージです。

JavaScript のプロパティアクセスとも違う

JavaScript では obj.items[0].name のように、オブジェクトのプロパティをドット記法やブラケット記法で参照します。これは実行中の JavaScript 式であり、JSONPath のような問い合わせ文字列ではありません。

たとえばキー名にドットが含まれる JSON では、違いがはっきり出ます。

{ "user.name": "Aki", "user": { "name": "Bun" } }
  • $.user.name: user の中の name
  • $['user.name']: user.name という 1 つのキー
  • obj.user.name: JavaScript のプロパティアクセス
  • obj["user.name"]: ドットを含むキーを JavaScript で参照

見た目が似ていても、文字列として保存するパスなのか、JavaScript として実行する式なのかで扱いが変わります。ユーザー入力をそのまま JavaScript 式として評価するのは危険なので、ツールでは構文を限定したパスとして扱うのが安全です。

lodash の path は便利だが標準JSONPathではない

lodash の _.get(object, path) は、"a[0].b.c" のような文字列や ["a", 0, "b", "c"] のような配列で深い値を取り出せます。これは実務で便利ですが、RFC 9535 の JSONPath と同じ構文ではありません。

lodash 系の path は、単一の値へたどる「プロパティパス」として使われることが多い一方、JSONPath はワイルドカード、子孫探索、フィルタなどにより複数ノードを選べます。つまり items[0].name のような単純ケースでは似ていても、items[*].name[?@.price < 2000] に進むと別物です。

実務でハマりやすい5つのケース

  1. ルート記号の有無: JSONPath は $ から始めることが多いが、jq は . から始まる。
  2. 配列の全要素: JSONPath は [*]、jq は [] を使う。
  3. ドットを含むキー: user.name というキーは ['user.name'] のように引用する。
  4. 存在しないキー: 実装によって空集合、nullundefined、エラーの扱いが違う。
  5. フィルタ構文: JSONPath の [?@.x] と jq の select(.x) は形も評価モデルも違う。

特に API レスポンス確認、ログ解析、テスト自動化では、別ツールのパスをそのまま移植して壊れることがよくあります。パスを共有するときは「これは JSONPath」「これは jq」「これは JavaScript/lodash path」と明記するだけで、かなり事故を減らせます。

人間が読むパスと機械が評価する式を分ける

画面上で「この値は items[0].name にあります」と示すだけなら、必ずしも完全な JSONPath である必要はありません。人間が読む説明用パスなら、短く読みやすい表記のほうが役に立つこともあります。

一方、実際に検索・抽出・テストに使う式は、評価エンジンに合わせた厳密な構文が必要です。JSONPath エンジンに渡すなら RFC 9535 の範囲を意識し、jq に渡すなら jq のフィルタとして書き、JavaScript で使うなら安全なプロパティアクセスや path 配列に変換します。

NanToo の JSONPath Finder のようなツールでは、JSON ツリーをクリックしてパスを確認できます。値の場所をまず視覚的に確認し、その後で JSONPath、JavaScript、Python、jq など用途別の表記へ切り替えると、手書きミスを減らせます。

参考文献・ソース

記事作成に関する注記

本記事は AI(大規模言語モデル)を編集補助として活用して作成しています。 公開前に編集者が内容を確認していますが、事実誤認・仕様の解釈ミス・最新情報との齟齬が含まれる可能性があります。 重要な判断を行う際は、本文中の一次ソースや公式ドキュメントを必ずご自身でご確認ください。 誤りにお気づきの場合は、お問い合わせフォームよりご連絡いただけると助かります。

🔧 関連ツール

📚 関連記事