JMESPath 完全指南
最后更新:2026-04-01 相关文档:内置函数完全参考(/python-nei-zhi-han-shu-wan-quan-can-kao/) 数据类型(/python-nei-zhi-shu-ju-lei-xing-wan-quan-can-kao/) Pydantic完全指南(/pydantic-wan-quan-zhi-nan/) JMESPath(JSON Matching Expression Paths)是一种 JSON 查询语言,可以从复杂的 JSON 结构中提取、过滤、转换数据,语法类似 XPath 之于 XML。 主要应用场景: 访问
最后更新:2026-04-01
官方文档:https://jmespath.org/specification.html
Python 库:https://github.com/jmespath/jmespath.py
适用版本:jmespath.py 1.0+(2026-05-08 核实)
相关文档:内置函数完全参考 数据类型 Pydantic完全指南
1. 基础概念
JMESPath 是什么
JMESPath(JSON Matching Expression Paths)是一种 JSON 查询语言,可以从复杂的 JSON 结构中提取、过滤、转换数据,语法类似 XPath 之于 XML。
主要应用场景:
- AWS CLI / boto3(大量使用 JMESPath 过滤 API 响应)
- Ansible、Salt 等运维工具的数据提取
- 接口测试框架中的响应断言
- 任意 Python 代码中处理嵌套 JSON
安装
pip install jmespath
基本用法
import jmespath
data = {
"user": {
"name": "Alice",
"age": 30,
"address": {
"city": "Beijing",
"zip": "100000"
}
}
}
jmespath.search("user.name", data) # "Alice"
jmespath.search("user.address.city", data) # "Beijing"
jmespath.search("user.missing", data) # None(不存在返回 None,不抛异常)
2. 基础表达式
标识符(Identifier)
访问对象的字段,支持嵌套:
data = {"a": {"b": {"c": 42}}}
jmespath.search("a", data) # {"b": {"c": 42}}
jmespath.search("a.b", data) # {"c": 42}
jmespath.search("a.b.c", data) # 42
字段名含特殊字符时,用双引号转义:
data = {"my-field": 1, "my field": 2}
jmespath.search('"my-field"', data) # 1
jmespath.search('"my field"', data) # 2
子表达式(Sub-expression)
用 . 连接多级路径(见上文)。
索引(Index)
访问数组元素,支持负索引:
data = {"names": ["Alice", "Bob", "Carol"]}
jmespath.search("names[0]", data) # "Alice"
jmespath.search("names[-1]", data) # "Carol"(最后一个)
jmespath.search("names[1]", data) # "Bob"
切片(Slice)
data = {"nums": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
jmespath.search("nums[0:5]", data) # [0, 1, 2, 3, 4]
jmespath.search("nums[5:]", data) # [5, 6, 7, 8, 9]
jmespath.search("nums[::2]", data) # [0, 2, 4, 6, 8](步长 2)
jmespath.search("nums[::-1]", data) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0](反转)
切片参数说明:[start:stop:step],与 Python 切片语法一致。
3. 通配符与投影
通配符(Wildcard)— *
对对象或数组的所有元素做投影:
data = {
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Carol", "age": 35},
]
}
# 提取所有用户的 name
jmespath.search("users[*].name", data)
# ["Alice", "Bob", "Carol"]
# 提取所有用户的多个字段(多级)
jmespath.search("users[*].age", data)
# [30, 25, 35]
对象通配符:
data = {
"servers": {
"web": {"ip": "1.1.1.1", "port": 80},
"db": {"ip": "2.2.2.2", "port": 5432},
}
}
jmespath.search("servers.*.ip", data)
# ["1.1.1.1", "2.2.2.2"]
列表投影(List Projection)
[*] 后接子表达式,对每个元素求值并收集结果(跳过 null):
data = {
"items": [
{"product": "Apple", "price": 5},
{"product": "Banana"}, # 无 price
{"product": "Cherry", "price": 3},
]
}
jmespath.search("items[*].price", data)
# [5, 3] — None 被自动过滤掉
对象投影(Object Projection)
.* 对对象的所有值做投影:
data = {
"metrics": {
"cpu": {"value": 80, "unit": "%"},
"mem": {"value": 60, "unit": "%"},
}
}
jmespath.search("metrics.*.value", data)
# [80, 60]
扁平化投影(Flatten)— []
将嵌套数组拍平一层,再进行投影:
data = {
"matrix": [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
}
jmespath.search("matrix[]", data)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
data2 = {
"orders": [
{"id": 1, "tags": ["urgent", "new"]},
{"id": 2, "tags": ["done"]},
]
}
jmespath.search("orders[].tags[]", data2)
# ["urgent", "new", "done"]
4. 过滤表达式(Filter)
语法:[?条件],筛选数组中满足条件的元素。
比较运算符
| 运算符 | 说明 |
|---|---|
== |
等于 |
!= |
不等于 |
< |
小于 |
<= |
小于等于 |
> |
大于 |
>= |
大于等于 |
data = {
"users": [
{"name": "Alice", "age": 30, "active": True},
{"name": "Bob", "age": 17, "active": False},
{"name": "Carol", "age": 25, "active": True},
]
}
# 年龄大于 18
jmespath.search("users[?age > `18`]", data)
# [{"name": "Alice", ...}, {"name": "Carol", ...}]
# 等于字符串(字符串用单引号)
jmespath.search("users[?name == 'Alice']", data)
# [{"name": "Alice", "age": 30, "active": true}]
# 布尔值过滤
jmespath.search("users[?active == `true`].name", data)
# ["Alice", "Carol"]
数字和布尔值用反引号 ` 包裹,字符串用单引号 ' 包裹。
逻辑运算符
# AND — &&
jmespath.search("users[?age > `18` && active == `true`].name", data)
# ["Alice", "Carol"]
# OR — ||
jmespath.search("users[?age < `20` || age > `28`].name", data)
# ["Alice", "Bob"]
# NOT — !
jmespath.search("users[?!active].name", data)
# ["Bob"]
过滤后提取字段
jmespath.search("users[?age >= `25`].name", data)
# ["Alice", "Carol"]
5. 多选(Multi-select)
多选列表(Multi-select List)— [expr, expr, ...]
从每个元素中提取多个字段,组成子数组:
data = {
"users": [
{"name": "Alice", "age": 30, "email": "[email protected]"},
{"name": "Bob", "age": 25, "email": "[email protected]"},
]
}
jmespath.search("users[*].[name, age]", data)
# [["Alice", 30], ["Bob", 25]]
多选哈希(Multi-select Hash)— {key: expr, key: expr}
从每个元素中提取多个字段,组成子对象(可重命名键):
jmespath.search("users[*].{username: name, years: age}", data)
# [{"username": "Alice", "years": 30}, {"username": "Bob", "years": 25}]
常用于重构 API 响应结构:
# AWS EC2 实例列表,只提取关键字段
jmespath.search(
"Reservations[].Instances[].[InstanceId, State.Name, PublicIpAddress]",
ec2_response
)
6. 内置函数
JMESPath 内置多个函数,可在表达式中直接使用。
数组/字符串函数
| 函数 | 说明 | 示例 |
|---|---|---|
length(expr) |
数组或字符串长度 | length(users) |
max(array) |
最大值(数字数组) | max(scores) |
min(array) |
最小值(数字数组) | min(scores) |
sum(array) |
求和(数字数组) | sum(prices) |
avg(array) |
平均值(数字数组) | avg(prices) |
sort(array) |
升序排序 | sort(names) |
sort_by(array, expr) |
按字段排序 | sort_by(users, &age) |
reverse(array) |
反转数组 | reverse(items) |
keys(obj) |
获取对象所有键 | keys(config) |
values(obj) |
获取对象所有值 | values(config) |
join(glue, array) |
用分隔符连接字符串数组 | join(', ', names) |
contains(array, val) |
是否包含某值 | contains(tags, 'admin') |
starts_with(str, prefix) |
字符串前缀匹配 | starts_with(name, 'A') |
ends_with(str, suffix) |
字符串后缀匹配 | ends_with(name, 'e') |
not_null(expr...) |
返回第一个非 null 值 | not_null(nickname, name) |
to_string(expr) |
转为字符串 | to_string(age) |
to_number(expr) |
转为数字 | to_number(price_str) |
type(expr) |
返回类型字符串 | type(value) |
floor(number) |
向下取整 | floor(avg(prices)) |
ceil(number) |
向上取整 | ceil(ratio) |
abs(number) |
绝对值 | abs(diff) |
merge(obj...) |
合并多个对象 | merge(defaults, config) |
data = {
"users": [
{"name": "Alice", "age": 30, "score": 88},
{"name": "Bob", "age": 25, "score": 92},
{"name": "Carol", "age": 35, "score": 76},
]
}
jmespath.search("length(users)", data) # 3
jmespath.search("max(users[*].score)", data) # 92
jmespath.search("avg(users[*].age)", data) # 30.0
jmespath.search("sort_by(users, &age)[*].name", data) # ["Bob", "Alice", "Carol"]
jmespath.search("join(', ', users[*].name)", data) # "Alice, Bob, Carol"
& 表达式(Expression Reference)
& 将表达式作为值传递给函数(不是立即求值):
# sort_by 需要传入表达式引用,而不是值
jmespath.search("sort_by(users, &age)", data) # 按 age 升序
jmespath.search("sort_by(users, &score)[*].name", data) # 按 score 升序取 name
过滤中使用函数
# 过滤 name 以 'A' 开头的用户
jmespath.search("users[?starts_with(name, 'A')].name", data)
# ["Alice"]
# 过滤 tags 中包含 'admin' 的记录
data2 = {
"accounts": [
{"user": "alice", "tags": ["admin", "dev"]},
{"user": "bob", "tags": ["dev"]},
]
}
jmespath.search("accounts[?contains(tags, 'admin')].user", data2)
# ["alice"]
7. 管道(Pipe)— |
将左侧结果作为右侧表达式的输入,用于链式操作:
data = {
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Carol", "age": 35},
]
}
# 先过滤,再取第一个元素的 name
jmespath.search("users[?age > `28`] | [0].name", data)
# "Alice"
# 先提取 name 数组,再取长度
jmespath.search("users[*].name | length(@)", data)
# 3 (@ 代表当前节点)
@ 表示当前节点,常用于管道右侧引用上一步的结果。
8. Python API
jmespath.search
import jmespath
result = jmespath.search(expression, data, options=None)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
expression |
str |
必填 | JMESPath 表达式字符串 |
data |
dict/list |
必填 | 待查询的 JSON 数据(Python 对象) |
options |
jmespath.Options |
None |
自定义选项(自定义函数等) |
预编译表达式(性能优化)
表达式解析有开销,对相同表达式多次查询时,预编译可提升性能:
import jmespath
# 预编译
expr = jmespath.compile("users[*].name")
# 多次复用
for data in datasets:
names = expr.search(data)
自定义函数
import jmespath
from jmespath import functions
class CustomFunctions(functions.Functions):
@functions.signature({"types": ["string"]})
def _func_upper(self, value):
"""自定义 upper() 函数"""
return value.upper()
@functions.signature({"types": ["array"]})
def _func_first_non_null(self, array):
"""返回数组中第一个非 null 值"""
for item in array:
if item is not None:
return item
return None
options = jmespath.Options(custom_functions=CustomFunctions())
data = {"names": ["alice", "bob"]}
jmespath.search("upper(names[0])", data, options=options)
# "ALICE"
9. 实战场景
处理 AWS API 响应
import boto3
import jmespath
ec2 = boto3.client("ec2")
response = ec2.describe_instances()
# 提取所有运行中实例的 ID 和 IP
instances = jmespath.search(
"Reservations[].Instances[?State.Name == 'running'].[InstanceId, PublicIpAddress]",
response
)
# 按标签过滤(找 Name=web 的实例)
web_instances = jmespath.search(
"Reservations[].Instances[?Tags[?Key=='Name' && Value=='web']].InstanceId",
response
)
接口测试响应断言
import jmespath
def assert_response(response_json: dict, expression: str, expected):
actual = jmespath.search(expression, response_json)
assert actual == expected, f"JMESPath '{expression}': expected {expected!r}, got {actual!r}"
# 使用
assert_response(resp, "code", 0)
assert_response(resp, "data.total", 100)
assert_response(resp, "data.items[0].name", "Alice")
assert_response(resp, "length(data.items)", 10)
配置文件提取
import json
import jmespath
with open("config.json") as f:
config = json.load(f)
# 提取所有数据库连接字符串
db_urls = jmespath.search("databases[*].url", config)
# 提取 enabled=true 的服务名称
enabled_services = jmespath.search("services[?enabled == `true`].name", config)
10. 最佳实践
预编译高频表达式
# 模块级别预编译,只解析一次
EXPR_USER_NAMES = jmespath.compile("users[*].name")
EXPR_ACTIVE_IDS = jmespath.compile("items[?status == 'active'].id")
def get_user_names(data):
return EXPR_USER_NAMES.search(data)
与 Pydantic 结合做响应映射
from pydantic import BaseModel
import jmespath
class UserSummary(BaseModel):
name: str
age: int
def extract_users(raw: dict) -> list[UserSummary]:
items = jmespath.search("data.users[*].{name: name, years: age}", raw)
return [UserSummary(name=i["name"], age=i["years"]) for i in (items or [])]
None 安全处理
jmespath.search 对不存在的路径返回 None,而不是抛异常,但若对 None 继续做操作需注意:
result = jmespath.search("a.b.c", data) or [] # 不存在时默认空列表
count = jmespath.search("length(items)", data) or 0
11. 踩坑与注意事项
数字和布尔值必须用反引号
在过滤表达式中,数字和布尔值必须用反引号包裹,否则会被解析为标识符:
# 错误 — age > 18 中的 18 被当作标识符
jmespath.search("users[?age > 18]", data) # 可能返回空或报错
# 正确 — 用反引号包裹字面量
jmespath.search("users[?age > `18`]", data)
jmespath.search("users[?active == `true`]", data)
字符串用单引号
# 正确
jmespath.search("users[?name == 'Alice']", data)
# 错误 — 双引号是标识符转义,不是字符串值
jmespath.search('users[?name == "Alice"]', data)
投影会自动过滤 null
[*] 投影会自动丢弃 null 结果,这在某些场景下可能导致数量对不上:
data = {"items": [{"v": 1}, {"x": 2}, {"v": 3}]}
jmespath.search("items[*].v", data)
# [1, 3] — {"x": 2}.v 是 None,被过滤掉
# 若需要保留 null,改用多选列表:
jmespath.search("items[*].[v]", data)
# [[1], [None], [3]]
表达式中的特殊字符字段名
字段名含 -、空格、@ 等特殊字符时,需用双引号转义:
data = {"x-api-key": "secret", "my data": [1, 2, 3]}
jmespath.search('"x-api-key"', data) # "secret"
jmespath.search('"my data"[0]', data) # 1
最佳实践
预编译表达式复用:频繁执行相同表达式时,jmespath.compile(expr) 返回的 Expression 对象支持多次 search(data),比每次调用 jmespath.search(expr, data) 更高效:
expr = jmespath.compile("items[?status=='active'].name")
for data in large_dataset:
names = expr.search(data)
用 ? 过滤替代 Python 列表推导:items[?type=='book'] 比先 jmespath.search('items', data) 再 Python 过滤更简洁,且逻辑内聚在查询表达式中。
@ 引用当前节点做多重过滤:items[?price > \100` && contains(tags, `sale`)]用&& 组合条件, `` ` 包裹字面量。
keys() / values() 提取字典键值:keys(metadata) 返回键名列表,values(metadata) 返回值列表,适合动态结构的 key 遍历。
结合 boto3 / AWS CLI 过滤响应:AWS SDK 返回的 JSON 响应层级深,JMESPath 是官方推荐的查询语言,aws ec2 describe-instances --query 'Reservations[].Instances[].PublicIpAddress' 直接在 CLI 层过滤。
常见陷阱
陷阱:字面量值必须用反引号包裹
现象: items[?status == 'active'] 报语法错误或不匹配任何结果。
原因: JMESPath 字面量(字符串、数字、布尔)必须用反引号(`)包裹,单引号不是字符串定界符。
解决: 改为 items[?status == `active`](反引号包裹字符串字面量)。
陷阱:[] 展平操作可能丢失层级信息
现象: outer[].inner 将所有 inner 展平为一维列表,但期望保留每个 outer 对应多个 inner 的分组关系。
原因: [] 是扁平化投影,把所有子列表合并成一个一维列表。
解决: 若需要保留外层结构,改用普通 subexpression outer[*].inner(不展平),或在 Python 层面分组处理。
陷阱:search 返回 None 时链式操作报错
现象: jmespath.search('missing.key', data) 返回 None,在外层取 len() 或 [0] 报 TypeError。
原因: JMESPath 找不到路径时返回 None 而非空列表或抛出异常。
解决: 始终检查返回值:result = expr.search(data) or [],或在调用端做 if result is None 判断。