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]。