JSON 工具箱

JMESPath 入門:比 JSONPath 更適合的投影查詢

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

入門約 9 分鐘發佈於 2026-10-31更新於 2026-10-31
JMESPath 入門封面

結論:只按位置定位用 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]。