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 判断。


参见

FastAPI完全指南
httpx完全指南

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog