JMESPath 入門:JSONPath を凌ぐ射影クエリ
要点:JMESPath はオブジェクトの束を JSONPath より直接的に再形成します。この入門ガイドでは構文、射影、パイプ、組み込み関数、AWS CLI での利用を扱います。

要点:位置指定には JSON Pointer、フィルタや再帰には JSONPath、結果を再形成したりコマンドラインで射影したりする必要があるなら JMESPath を使いましょう。既知の単一フィールドにはどれも不要です。
オブジェクトの束を別の形に作り直すのが目的なら、JMESPath はしばしば JSONPath より直接的です。ノードを探すのではなく、結果を組み立てるのです。
1. JMESPath とは
JMESPath は JSON 専用に設計されたクエリ言語です。もともと AWS CLI の出力をフィルタするために作られ、後に jmespath.js、jmespath.py、go-jmespath などの実装を通じて多くの言語に広がりました。その目標は JSONPath と異なります。
- JSONPath はパスと条件でノードの集合を選択することに焦点を当て、ノード集合を返します。
- JMESPath は入力 JSON を変換することに焦点を当て、任意の形の値を返します。
この違いにより、JMESPath はリストの射影、フィールド名の変更、結果の刈り込みに特に優れます。たとえば API レスポンスの 20 フィールドを、フロントエンドが実際に必要とする 3 フィールドに減らせます。JSONPath の基礎をまだ読んでいなければ、JSONPath クエリ言語の実践から始めてください。
2. 基本構文:識別子からインデックスまで
JMESPath は JSONPath より記号が少なく、抽出式のように読めます。
| 構文 | 意味 | 例 |
|---|---|---|
name |
オブジェクトのメンバーにアクセス | people |
a.b |
ネストしたメンバーにアクセス | store.bicycle.color |
[0] |
配列のインデックスにアクセス | people[0] |
[-1] |
最後の要素にアクセス | people[-1] |
[0:2] |
スライス。開始を含み終了を含まない | people[0:2] |
[*] |
リスト射影 | people[*].name |
[] |
平坦化射影 | people[].tags[] |
[?condition] |
フィルタ射影 | people[?age > 18] |
{key: expr} |
複数選択ハッシュ | {name: name, age: age} |
[expr, expr] |
複数選択リスト | [name, age] |
| |
左の結果を右へパイプ | people[*].age | [0] |
最小の例:
{
"people": [
{ "name": "Alice", "age": 31, "city": "Shanghai" },
{ "name": "Bob", "age": 17, "city": "Beijing" }
]
}
people[0].name → "Alice"
people[-1].city → "Beijing"
people[0:2].name → ["Alice", "Bob"]
people[0:2].name が配列を返す理由に注意してください。スライスはリストを返し、続く .name はそのリストのすべての要素に自動で射影されます。これが JMESPath と通常のオブジェクトアクセスの最大の違いです。
3. 射影:一つのリストを別のリストに変える
射影は JMESPath の中核概念です。二段階で動作します。リストのすべての要素を評価し、結果を新しいリストに集める。null に評価される要素は自動的に落とされます。
people[*].name // ["Alice", "Bob"]
people[*].{name: name, age: age} // オブジェクトのリスト
people[?age > 18].name // ["Alice"]
二行目は典型的な射影式です。people[*].{name: name, age: age} はすべての要素を { "name": ..., "age": ... } に作り直し、余分な city フィールドを捨てます。ノードを返してコードでマップする JSONPath と比べ、ループを一つ省けます。
区別すべき射影は二種類です。
[*]は元のリスト構造を保ち、null 値をスキップします。[]は平坦化射影で、ネストしたリストを一段階まず畳みます。「すべての人のすべてのタグ」が必要なpeople[].tags[]のような式に適します。
4. パイプと複数選択
パイプ | は「現在の射影を止め、結果を新しい入力として扱う」ことを意味します。最も一般的な用途は射影の後にインデックスを取ることです。
people[*].name | [0] → "Alice"
people[*].age | sort(@) | [-1] → 最大の年齢
パイプがなければ、people[*].name[0] はすべての名前の最初の文字を取ると解釈され、望む結果になりません。パイプは JMESPath で最も見落とされ、最も有用な記号です。
複数選択には二つの形があります。
// 複数選択ハッシュ:結果はオブジェクト
people[0].{name: name, age: age}
// 複数選択リスト:結果は配列
people[0].[name, age]
複数選択ハッシュは名前付きの結果を組み立てるのに、複数選択リストは位置で消費するのに適します。どちらも射影内にネストできますが、注意してください。複数選択ハッシュでは値が null のキーも残りますが、射影は null の要素全体を落とします。
5. 組み込み関数
JMESPath 標準は一連の関数を定義しています。よく使うもの:
| 関数 | 目的 | 例 |
|---|---|---|
length() |
長さを取得 | length(people) |
sort() |
値を並び替え | sort(people[*].age) |
sort_by() |
フィールドで並び替え | sort_by(people, &age) |
max() / min() |
最大 / 最小 | max(people[*].age) |
sum() |
値を合計 | sum(people[*].age) |
keys() |
オブジェクトのキーを取得 | keys(people[0]) |
contains() |
包含を確認 | contains(name, "Ali") |
to_string() |
文字列に変換 | to_string(age) |
sort_by の第 2 引数は & 式で、「各要素からこのフィールドを取る」ことを意味します。
sort_by(people, &age)[*].name
これらの関数により JMESPath は簡単な集計を行えますが、JSONPath は選択のためだけに設計されており、合計やグループ化は通常 jq かコードが必要です。
6. JSONPath との並列比較
| 観点 | JSONPath | JMESPath |
|---|---|---|
| 主な目標 | ノードを選択 | データを変換 |
| ルート記号 | $ |
なし。フィールド名を直接書く |
| 現在ノード | @ |
@(関数引数内のみ) |
| フィルタ | [?(@.age > 18)] |
[?age > 18] |
| ワイルドカード | [*] |
[*] または [] |
| 新しいオブジェクトの構築 | 非対応 | {name: name} |
| パイプ | 非対応 | | |
| 標準 | RFC 9535 | JMESPath Specification |
一文の判断指針:
- 条件に一致するノードを見つけるだけなら JSONPath を使う。
- 結果をフロントエンドが期待する形に再形成するなら JMESPath を使う。
- 既知の単一フィールドを読むだけならどちらも使わず、プロパティに直接アクセスする。
クエリ結果を文書に書き戻す必要があるなら、どちらの言語も適しません。JSON Pointer とパッチを使ってください。JSON Pointer、Patch、Merge Patchを参照してください。
7. AWS CLI の JMESPath
AWS CLI の --query パラメータは JMESPath で、最も広く使われる用途です。
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id: InstanceId, State: State.Name, Type: InstanceType}' \
--output table
このコマンドは三つのことをします。[] で二段のネストしたリストを平坦化し、複数選択ハッシュで 3 フィールドだけを残し、--output table で表として描画します。JSONPath ではより多くの階層が必要で、「名前変更と刈り込み」の効果は得られません。
他のよくある形:
# 実行中のインスタンスのみ
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,PublicIpAddress]'
# Name タグの値
aws ec2 describe-instances --query 'Reservations[].Instances[].Tags[?Key==`Name`].Value[]'
バッククォートはリテラルを囲み、文字列ではありません。AWS CLI では文字列に単一引用符、JSON リテラルにバッククォートを使います。この違いは初心者が最もよくする間違いの一つです。
8. よくある間違い
[*]と[]の混同:前者はネストを保ち、後者は一段階平坦化し、結果の構造はまったく異なります。- パイプを忘れる:
people[*].name[0]は各名前の最初の文字を取り、最初の名前ではありません。 - フィルタ内で
@を書く:JMESPath のフィルタはフィールド名を直接使うので、[?@.age > 18]は誤りです。 nullを空文字列として扱う:射影はnullを落としますが、複数選択ハッシュはキーを残します。- AWS CLI でリテラルを二重引用符で囲む:シェルが先に展開し、式を書き換えてしまいます。
デバッグの助言:式はできるだけ小さな入力で検証してから実データに適用し、AWS CLI では --output json を足して生の結果を見ます。日常の JSON 編集と整形には JSON エディタとコマンドラインツールを組み合わせると、フィールドパスの特定がずっと速くなります。
FAQ
JMESPath と JSONPath は併用できますか?
できますが、通常は不要です。よくある方法は、JMESPath で小さなオブジェクトを刈り込み、コードでは普通のプロパティアクセスを使うことです。どうしてもパスで位置特定してから変更する必要があるなら、JSONPath が読み、JSON Pointer が書くという分担の方が明確です。
people[*].name が単一の文字列以外を返すのはなぜですか?
[*] が射影で、結果は常にリストだからです。人が一人だけでも同じです。単一値を得るにはパイプとインデックスを足します。people[*].name | [0]。
射影内の null はどうなりますか?
リスト射影は null に評価される要素を自動的にスキップするので、結果は元のリストより短くなることがあります。複数選択ハッシュはキーを残し null 値も保つので、API レスポンスを組み立てるときに重要です。
JMESPath は集計とグループ化ができますか?
sum()、max()、sort_by() で基本的な集計ができます。グループ化、結合、ウィンドウ関数は標準の外です。複雑な分析は jq、SQL、コードに任せてください。
JMESPath と JSONPath はどう決めればよいですか?
パスと条件でノードを選択してノード集合を受け取るなら JSONPath を使います。結果をフロントエンドが期待する形に再形成し、フィールド名の変更や刈り込みを行うなら JMESPath を使います。既知の単一フィールドを読むだけならどちらも使わず、プロパティにアクセスしてください。
射影の最初の値を取るのにパイプを使うべきですか?
はい。people[*].name は射影なので結果は常にリストです。パイプがなければ people[*].name[0] は各名前の最初の文字を取ります。最初の値を得るには people[*].name | [0] と書きます。