> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# JMESPath 完全指南
- URL: https://blog.vercanti.com/jmespath-wan-quan-zhi-nan/
- Published: 2026-08-28T14:34:31.000Z
- Updated: 2026-08-28T14:56:41.000Z
- Description: 最后更新：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。 主要应用场景： 访问
- Author: yellowdog
- Tags: Python, 基础

最后更新：2026-04-01

> 官方文档：<https://jmespath.org/specification.html>  
> Python 库：<https://github.com/jmespath/jmespath.py>  
> 适用版本：jmespath.py 1.0+（2026-05-08 核实）

相关文档：[内置函数完全参考](https://blog.vercanti.com/python-nei-zhi-han-shu-wan-quan-can-kao/) [数据类型](https://blog.vercanti.com/python-nei-zhi-shu-ju-lei-xing-wan-quan-can-kao/) [Pydantic完全指南](https://blog.vercanti.com/pydantic-wan-quan-zhi-nan/)

---

## 1\. 基础概念

### JMESPath 是什么

JMESPath（JSON Matching Expression Paths）是一种 JSON 查询语言，可以从复杂的 JSON 结构中提取、过滤、转换数据，语法类似 XPath 之于 XML。

主要应用场景：

- AWS CLI / boto3（大量使用 JMESPath 过滤 API 响应）
- Ansible、Salt 等运维工具的数据提取
- 接口测试框架中的响应断言
- 任意 Python 代码中处理嵌套 JSON

### 安装

```bash
pip install jmespath

```

### 基本用法

```python
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）

访问对象的字段，支持嵌套：

```python
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

```

字段名含特殊字符时，用双引号转义：

```python
data = {"my-field": 1, "my field": 2}

jmespath.search('"my-field"', data)  # 1
jmespath.search('"my field"', data)  # 2

```

### 子表达式（Sub-expression）

用 `.` 连接多级路径（见上文）。

### 索引（Index）

访问数组元素，支持负索引：

```python
data = {"names": ["Alice", "Bob", "Carol"]}

jmespath.search("names[0]", data)   # "Alice"
jmespath.search("names[-1]", data)  # "Carol"（最后一个）
jmespath.search("names[1]", data)   # "Bob"

```

### 切片（Slice）

```python
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）— `*`

对对象或数组的所有元素做投影：

```python
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]

```

对象通配符：

```python
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`）：

```python
data = {
    "items": [
        {"product": "Apple", "price": 5},
        {"product": "Banana"},          # 无 price
        {"product": "Cherry", "price": 3},
    ]
}

jmespath.search("items[*].price", data)
# [5, 3]  — None 被自动过滤掉

```

### 对象投影（Object Projection）

`.*` 对对象的所有值做投影：

```python
data = {
    "metrics": {
        "cpu": {"value": 80, "unit": "%"},
        "mem": {"value": 60, "unit": "%"},
    }
}

jmespath.search("metrics.*.value", data)
# [80, 60]

```

### 扁平化投影（Flatten）— `[]`

将嵌套数组拍平一层，再进行投影：

```python
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）

语法：`[?条件]`，筛选数组中满足条件的元素。

### 比较运算符

| 运算符 | 说明   |
| --- | ---- |
| \== | 等于   |
| !=  | 不等于  |
| <   | 小于   |
| <=  | 小于等于 |
| \>  | 大于   |
| \>= | 大于等于 |

```python
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"]

```

数字和布尔值用反引号 `` ` `` 包裹，字符串用单引号 `'` 包裹。

### 逻辑运算符

```python
# 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"]

```

### 过滤后提取字段

```python
jmespath.search("users[?age >= `25`].name", data)
# ["Alice", "Carol"]

```

---

## 5\. 多选（Multi-select）

### 多选列表（Multi-select List）— `[expr, expr, ...]`

从每个元素中提取多个字段，组成子数组：

```python
data = {
    "users": [
        {"name": "Alice", "age": 30, "email": "alice@example.com"},
        {"name": "Bob",   "age": 25, "email": "bob@example.com"},
    ]
}

jmespath.search("users[*].[name, age]", data)
# [["Alice", 30], ["Bob", 25]]

```

### 多选哈希（Multi-select Hash）— `{key: expr, key: expr}`

从每个元素中提取多个字段，组成子对象（可重命名键）：

```python
jmespath.search("users[*].{username: name, years: age}", data)
# [{"username": "Alice", "years": 30}, {"username": "Bob", "years": 25}]

```

常用于重构 API 响应结构：

```python
# 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)   |

```python
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）

`&` 将表达式作为值传递给函数（不是立即求值）：

```python
# sort_by 需要传入表达式引用，而不是值
jmespath.search("sort_by(users, &age)", data)         # 按 age 升序
jmespath.search("sort_by(users, &score)[*].name", data)  # 按 score 升序取 name

```

### 过滤中使用函数

```python
# 过滤 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）— `|`

将左侧结果作为右侧表达式的输入，用于链式操作：

```python
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`

```python
import jmespath

result = jmespath.search(expression, data, options=None)

```

| 参数         | 类型               | 默认值  | 说明                      |
| ---------- | ---------------- | ---- | ----------------------- |
| expression | str              | 必填   | JMESPath 表达式字符串         |
| data       | dict/list        | 必填   | 待查询的 JSON 数据（Python 对象） |
| options    | jmespath.Options | None | 自定义选项（自定义函数等）           |

### 预编译表达式（性能优化）

表达式解析有开销，对相同表达式多次查询时，预编译可提升性能：

```python
import jmespath

# 预编译
expr = jmespath.compile("users[*].name")

# 多次复用
for data in datasets:
    names = expr.search(data)

```

### 自定义函数

```python
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 响应

```python
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
)

```

### 接口测试响应断言

```python
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)

```

### 配置文件提取

```python
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\. 最佳实践

### 预编译高频表达式

```python
# 模块级别预编译，只解析一次
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 结合做响应映射

```python
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` 继续做操作需注意：

```python
result = jmespath.search("a.b.c", data) or []  # 不存在时默认空列表
count = jmespath.search("length(items)", data) or 0

```

---

## 11\. 踩坑与注意事项

### 数字和布尔值必须用反引号

在过滤表达式中，数字和布尔值必须用反引号包裹，否则会被解析为标识符：

```python
# 错误 — age > 18 中的 18 被当作标识符
jmespath.search("users[?age > 18]", data)    # 可能返回空或报错

# 正确 — 用反引号包裹字面量
jmespath.search("users[?age > `18`]", data)
jmespath.search("users[?active == `true`]", data)

```

### 字符串用单引号

```python
# 正确
jmespath.search("users[?name == 'Alice']", data)

# 错误 — 双引号是标识符转义，不是字符串值
jmespath.search('users[?name == "Alice"]', data)

```

### 投影会自动过滤 null

`[*]` 投影会自动丢弃 `null` 结果，这在某些场景下可能导致数量对不上：

```python
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]]

```

### 表达式中的特殊字符字段名

字段名含 `-`、空格、`@` 等特殊字符时，需用双引号转义：

```python
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)` 更高效：

```python
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完全指南](https://blog.vercanti.com/fastapi-wan-quan-zhi-nan/)  
[httpx完全指南](https://blog.vercanti.com/httpx-wan-quan-zhi-nan/)