JSONPathクエリガイド:プロのようにデータを抽出する
JSONPathはJSONのクエリ言語で、XMLに対するXPathと同様の役割を果たします。APIからの複雑で深くネストされたJSONレスポンスを扱う場合、JSONPathを使えばループや条件分岐を書かずに必要なデータを正確に抽出できます。このガイドでは、構文、演算子、実践的なパターンを解説します。
なぜJSONPathか?
ネストされたデータを持つ典型的なAPIレスポンスを考えてみましょう:
{
"store": {
"books": [
{ "title": "Clean Code", "author": "Robert Martin", "price": 32.99, "tags": ["programming", "best-practices"] },
{ "title": "Design Patterns", "author": "Gang of Four", "price": 44.99, "tags": ["programming", "architecture"] },
{ "title": "The Pragmatic Programmer", "author": "Hunt & Thomas", "price": 39.99, "tags": ["programming", "career"] }
],
"music": [
{ "title": "Kind of Blue", "artist": "Miles Davis", "price": 12.99 }
]
}
}
すべての本のタイトルを取得するには、ループを書くこともできますが、JSONPathなら $.store.books[*].title で済みます。
基本構文
| 式 | 説明 |
|---|---|
$ | ルートオブジェクト |
. | 子演算子 |
.. | 再帰降下(すべてのレベルを検索) |
[*] | ワイルドカード(すべての要素) |
[n] | 配列インデックス(0始まり) |
[n,m] | 複数インデックス |
[start:end:step] | 配列スライス |
[?()] | フィルター式 |
@ | 現在の要素(フィルター内) |
ドット記法 vs ブラケット記法
どちらの記法もプロパティにアクセスしますが、特殊文字にはブラケット記法が必要です:
# ドット記法
$.store.books[0].title
# ブラケット記法(同等)
$['store']['books'][0]['title']
# 特殊文字を含むキーには必須
$['store']['price-range']
配列操作
インデックス指定
$.store.books[0] # 最初の本
$.store.books[-1] # 最後の本
$.store.books[0,2] # 最初と3番目の本
スライス
$.store.books[0:2] # 最初の2冊(インデックス0と1)
$.store.books[1:] # 最初を除くすべての本
$.store.books[:2] # 最初の2冊
$.store.books[::2] # 1つおきの本
ワイルドカード
$.store.books[*].title # すべての本のタイトル
$.store.* # すべてのストアコレクション(books、music)
$..title # あらゆる深さのすべてのタイトル
$..price # あらゆる深さのすべての価格
再帰降下演算子(..)は、階層内の位置に関係なく値を抽出する場合に特に強力です。
フィルター式
フィルターは条件に基づいて要素を選択します:
# $40未満の本
$.store.books[?(@.price < 40)]
# 特定の著者の本
$.store.books[?(@.author == 'Robert Martin')]
# タグが2つ以上の本
$.store.books[?(@.tags.length > 2)]
# 'price'プロパティを持つ本
$.store.books[?(@.price)]
フィルターの組み合わせ
# $40未満でprogrammingタグの本
$.store.books[?(@.price < 40 && @.tags[0] == 'programming')]
実践的な例
APIレスポンスからの抽出
GitHub API — すべてのリポジトリ名を取得:
$[*].name
天気API — 今日の気温を取得:
$.daily[0].temp.day
ECサイトAPI — すべての商品画像を取得:
$.products[*].images[0].url
設定の抽出
Docker Compose — すべてのサービス名を取得:
$.services.*~
Package.json — すべての依存関係名を取得:
$.dependencies.*~
JSONPath vs jq
JSONPathとjqはどちらもJSONクエリツールですが、使用コンテキストが異なります:
| 機能 | JSONPath | jq |
|---|---|---|
| 環境 | ライブラリ、API | コマンドライン |
| 構文 | XPathインスパイア | カスタム関数型 |
| 変換 | クエリのみ | クエリ + 変換 |
| 標準 | RFC 9535 | デファクトスタンダード |
コマンドラインでのJSON処理にはjqの方が強力です。アプリケーションへのクエリ埋め込みやWebツールでの使用には、JSONPathの方が広くサポートされています。
JSON Path ExplorerでJSONPath式をインタラクティブに試せます。JSONを貼り付け、クエリを書き、結果を即座に確認できます。
実装例
JavaScript(jsonpath-plus)
const { JSONPath } = require('jsonpath-plus');
const result = JSONPath({
path: '$.store.books[?(@.price < 40)].title',
json: data
});
// ["Clean Code", "The Pragmatic Programmer"]
Python(jsonpath-ng)
from jsonpath_ng import parse
expr = parse('$.store.books[*].title')
titles = [match.value for match in expr.find(data)]
RFC 9535:JSONPath標準
2024年2月、JSONPathはRFC 9535として正式に標準化されました。これにより実装間の歴史的な不整合が解消されます。標準化された主な動作:
- 配列インデックスは0始まり
- フィルター式は現在の要素に
@を使用 - 文字列比較は大文字小文字を区別
- 結果は常にマッチした値の配列
JSONPathライブラリを選ぶ際は、一貫した動作のためにRFC 9535をサポートするものを優先してください。
FAQ
JSONPathとJSON Pointerの違いは何ですか?
JSONPathはワイルドカードとフィルターを使用して複数の値にマッチできるクエリ言語です。JSON Pointer(RFC 6901)は正確に1つの値をアドレスするシンプルなパス構文です:/store/books/0/title。検索やフィルタリングが必要な場合はJSONPathを、正確なパスがわかっている場合はJSON Pointerを使用してください。
JSONPathでJSONデータを変更できますか?
標準のJSONPathは読み取り専用で、抽出はしますが変更はしません。一部のライブラリはset/delete操作でJSONPathを拡張していますが、これらは非標準です。変換にはjq(コマンドライン)を使用するか、アプリケーションコードを書いてください。JSON構造の表示と探索には、クエリを書く前にデータを理解するためにJSONフォーマッターが役立ちます。
関連リソース
- JSON Path Explorer — ブラウザでJSONPath式をテスト
- JSONフォーマットベストプラクティス — クエリしやすいJSONの構造化
- JSONスキーマバリデーションガイド — JSONPathクエリが期待する構造のバリデーション