JMESPath 入門:比 JSONPath 更適合的投影查詢
結論:如果目標是把一批物件重排成另一種形狀,JMESPath 比 JSONPath 更直接。本文從基礎語法講到投影、管道、內建函式與 AWS CLI 實戰。

結論:只按位置定位用 JSON Pointer,要篩選或遞迴用 JSONPath,要把結果重排成另一種形狀或在命令列做投影就用 JMESPath;只讀一個確定欄位兩個都不需要。
如果你的目標是「把一批物件重排成另一種形狀」,JMESPath 往往比 JSONPath 更直接——它不是去找節點,而是去構造結果。
一、JMESPath 是什麼
JMESPath 是一種專門為 JSON 設計的查詢語言,最初為 AWS CLI 的輸出過濾而設計,後來被 jmespath.js、jmespath.py、go-jmespath 等實現帶到各種語言裡。它和 JSONPath 的目標不同:
- JSONPath 的核心是「按路徑和條件選出一批節點」,返回的是節點集;
- JMESPath 的核心是「對輸入 JSON 做變換」,返回的是任意形狀的新值。
這個定位差異決定了 JMESPath 特別適合做列表投影、欄位重新命名、結果裁剪,例如把介面返回的 20 個欄位裁成前端需要的 3 個欄位。如果你還沒有讀過 JSONPath 的基礎語法,可以先看 JSONPath 查詢語言實戰。
二、基礎語法:從識別符號到索引
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 和普通物件取值的最大區別。
三、投影:把列表變成另一種列表
投影(projection)是 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 拿到節點後再在程式碼裡映射要少一層迴圈。
兩種投影值得區分:
[*]保留原列表結構,空值被跳過;[]是展平投影,會先把巢狀列表攤平一層,適合people[].tags[]這種「取所有人的所有標籤」。
四、管道與多選
管道 | 用來「停下當前投影,把結果當成新的輸入」。它最常見的用途是投影之後再取下標:
people[*].name | [0] → "Alice"
people[*].age | sort(@) | [-1] → 最大年齡
如果不寫管道,people[*].name[0] 會被理解為對每個 name 取第 0 個字元,結果完全不是你要的。管道是 JMESPath 裡最容易被忽略、也最容易救命的符號。
多選有兩種:
// 多選雜湊:結果是一個物件
people[0].{name: name, age: age}
// 多選列表:結果是一個陣列
people[0].[name, age]
多選雜湊適合構造具名結果,多選列表適合按位置消費。兩者都可以巢狀在投影裡,但要注意:多選雜湊裡如果某個鍵的值為 null,該鍵仍然保留;投影則會把整個 null 元素丟掉。
五、內建函式
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 的第二個參數要用 & 表示式,表示「對每個元素取這個欄位」:
sort_by(people, &age)[*].name
這套函式讓 JMESPath 能完成簡單的聚合,而 JSONPath 的定位是「只做選擇」,求和、分組通常要換 jq 或程式碼。
六、與 JSONPath 的對照
| 維度 | JSONPath | JMESPath |
|---|---|---|
| 主要目標 | 選節點 | 變換資料 |
| 根符號 | $ |
無,直接寫欄位名 |
| 當前節點 | @ |
@(僅函式參數中) |
| 過濾 | [?(@.age > 18)] |
[?age > 18] |
| 通配 | [*] |
[*] 或 [] |
| 構造新物件 | 不支援 | {name: name} |
| 管道 | 不支援 | | |
| 標準 | RFC 9535 | JMESPath Specification |
一句話選擇:
- 你只需要「找出符合條件的節點」,用 JSONPath;
- 你需要「把結果重排成前端要的形狀」,用 JMESPath;
- 你需要「讀一個確定的欄位」,兩個都不需要,直接取屬性。
如果查詢結果還要寫回文件,這兩套語言都不適合,應該改用 JSON Pointer 與補丁,參見 JSON Pointer、Patch 與 Merge Patch。
七、AWS CLI 裡的實戰
AWS CLI 的 --query 參數就是 JMESPath,這是它最廣泛的使用場景:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id: InstanceId, State: State.Name, Type: InstanceType}' \
--output table
這條命令做了三件事:用 [] 展平兩層巢狀列表,用多選雜湊只保留三個欄位,再用 --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 字面量用反引號,這個差異是新手最常見的錯誤之一。
八、常見錯誤
- 把
[*]和[]混用:前者保留巢狀,後者展平一層,結果結構完全不同; - 忘記管道:
people[*].name[0]取的是每個名字的首字元,不是第一個名字; - 在過濾器裡寫
@:JMESPath 的過濾條件直接用欄位名,[?@.age > 18]是錯的; - 把
null當成空字串:投影會丟棄null,但多選雜湊會保留鍵; - 在 AWS CLI 裡用雙引號包字面量:shell 會先展開,導致表示式被改寫。
除錯建議:先用最小輸入驗證表示式,再套到真實資料;AWS CLI 可以加 --output json 看原始結果。日常編輯與格式化 JSON 時,配合 JSON 編輯器與命令列工具 能更快定位欄位路徑。
常見問題
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] 會被理解為對每個名字取第 0 個字元。取第一個值要寫成 people[*].name | [0]。