Pytest 的 JSONPath 详解
1. 什么是 JSONPath
1.1 JSONPath 的概念
JSONPath 是一种用于从 JSON 文档中提取数据的查询语言,类似于 XPath 用于 XML 文档。它提供了一种简洁、直观的方式来定位和提取 JSON 数据中的特定部分。
1.2 为什么需要 JSONPath
在 API 测试中,我们经常需要从复杂的 JSON 响应中提取数据。传统的 Python 字典访问方式(如 response["data"]["user"]["id"])虽然可行,但存在以下问题:
- 代码冗长:嵌套层级深时,代码会变得很长
- 容易出错:键名拼写错误会导致 KeyError
- 不够灵活:难以处理动态结构或数组数据
- 可读性差:多层嵌套的字典访问不够直观
JSONPath 解决了这些问题,提供了更优雅、更强大的数据提取方式。
1.3 JSONPath 的优势
- 简洁直观:使用类似文件路径的语法,易于理解
- 功能强大:支持通配符、过滤、切片等高级功能
- 跨语言:JSONPath 是标准化的,可以在多种编程语言中使用
- 灵活性强:可以处理复杂的嵌套结构和数组
1.4 JSONPath 与 Python 字典访问的对比
让我们看一个简单的对比示例:
# 假设我们有这样的 JSON 响应
response_data = {
"code": 200,
"message": "success",
"data": {
"users": [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"},
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}
],
"total": 3
}
}
# 传统方式:提取第一个用户的邮箱
email = response_data["data"]["users"][0]["email"]
print(email) # 输出: alice@example.com
# JSONPath 方式:提取第一个用户的邮箱
from jsonpath_ng import parse
jsonpath_expr = parse("$.data.users[0].email")
matches = [match.value for match in jsonpath_expr.find(response_data)]
email = matches[0] if matches else None
print(email) # 输出: alice@example.com
# JSONPath 方式:提取所有用户的邮箱(更强大)
jsonpath_expr = parse("$.data.users[*].email")
matches = [match.value for match in jsonpath_expr.find(response_data)]
print(matches) # 输出: ['alice@example.com', 'bob@example.com', 'charlie@example.com']
可以看到,JSONPath 在处理数组数据时更加灵活和强大。
2. 安装 JSONPath 库
2.1 可用的 JSONPath 库
Python 中有几个流行的 JSONPath 库:
- jsonpath-ng:功能最全面,支持完整的 JSONPath 规范
- jsonpath-rw:另一个流行的实现
- jsonpath:轻量级的实现
我们推荐使用 jsonpath-ng,因为它功能最完整,文档最详细。
2.2 安装 jsonpath-ng
# 使用 pip 安装
pip install jsonpath-ng
# 使用 pip 从国内镜像源安装(推荐)
pip install jsonpath-ng -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装指定版本
pip install jsonpath-ng==1.6.0
2.3 验证安装
安装完成后,可以通过以下方式验证:
# 方式一:检查版本
import jsonpath_ng
print(jsonpath_ng.__version__) # 输出版本号
# 方式二:尝试导入和使用
from jsonpath_ng import parse
print("安装成功!")
# 方式三:简单测试
data = {"name": "test"}
jsonpath_expr = parse("$.name")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['test']
3. JSONPath 基础语法
3.1 JSONPath 表达式结构
JSONPath 表达式以 $ 开头,表示 JSON 文档的根节点。然后使用点号(.)或方括号([])来访问子节点。
3.2 基本操作符
3.2.1 根节点操作符
$:表示 JSON 文档的根节点
from jsonpath_ng import parse
data = {"name": "Alice", "age": 30}
# 访问根节点
jsonpath_expr = parse("$")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: [{'name': 'Alice', 'age': 30}]
3.2.2 子节点操作符
.:点号用于访问对象的属性[]:方括号用于访问数组元素或对象的属性
from jsonpath_ng import parse
data = {
"user": {
"name": "Alice",
"age": 30
},
"items": [1, 2, 3]
}
# 使用点号访问属性
jsonpath_expr = parse("$.user.name")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['Alice']
# 使用方括号访问属性
jsonpath_expr = parse("$['user']['name']")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['Alice']
# 访问数组元素
jsonpath_expr = parse("$.items[0]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: [1]
3.2.3 通配符操作符
- *``**:匹配所有元素或属性
from jsonpath_ng import parse
data = {
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Charlie", "age": 35}
]
}
# 匹配所有用户
jsonpath_expr = parse("$.users[*]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches)
# 输出: [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}, {'name': 'Charlie', 'age': 35}]
# 匹配所有用户的姓名
jsonpath_expr = parse("$.users[*].name")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['Alice', 'Bob', 'Charlie']
3.2.4 数组切片操作符
[start:end:step]:Python 风格的数组切片
from jsonpath_ng import parse
data = {
"numbers": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
# 获取前三个元素
jsonpath_expr = parse("$.numbers[0:3]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: [0, 1, 2]
# 获取所有偶数索引的元素
jsonpath_expr = parse("$.numbers[0::2]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: [0, 2, 4, 6, 8]
# 获取最后三个元素
jsonpath_expr = parse("$.numbers[-3:]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: [7, 8, 9]
3.2.5 递归下降操作符
..:递归搜索所有匹配的节点
from jsonpath_ng import parse
data = {
"store": {
"book": [
{"title": "Book 1", "author": "Author 1"},
{"title": "Book 2", "author": "Author 2"}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
# 递归查找所有 price 字段
jsonpath_expr = parse("$..price")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: [19.95]
# 递归查找所有 title 字段
jsonpath_expr = parse("$..title")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['Book 1', 'Book 2']
3.3 完整示例:理解 JSONPath 语法
让我们通过一个完整的示例来理解各种 JSONPath 语法:
from jsonpath_ng import parse
# 复杂的 JSON 数据结构
data = {
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
# 1. 获取所有书籍
jsonpath_expr = parse("$.store.book[*]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"所有书籍数量: {len(matches)}") # 输出: 4
# 2. 获取所有书籍的价格
jsonpath_expr = parse("$.store.book[*].price")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"所有价格: {matches}") # 输出: [8.95, 12.99, 8.99, 22.99]
# 3. 获取第一本书
jsonpath_expr = parse("$.store.book[0]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"第一本书: {matches[0]['title']}") # 输出: Sayings of the Century
# 4. 获取最后一本书
jsonpath_expr = parse("$.store.book[-1]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"最后一本书: {matches[0]['title']}") # 输出: The Lord of the Rings
# 5. 获取前两本书
jsonpath_expr = parse("$.store.book[0:2]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"前两本书数量: {len(matches)}") # 输出: 2
# 6. 递归查找所有价格
jsonpath_expr = parse("$..price")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"所有价格(递归): {matches}") # 输出: [8.95, 12.99, 8.99, 22.99, 19.95]
# 7. 获取所有有 ISBN 的书籍
jsonpath_expr = parse("$.store.book[?(@.isbn)]")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"有 ISBN 的书籍数量: {len(matches)}") # 输出: 2
4. 在 Pytest 中使用 JSONPath
4.1 基本使用方式
在 pytest 测试中使用 JSONPath 提取数据非常简单:
import pytest
from jsonpath_ng import parse
def test_extract_simple_value():
"""测试提取简单值"""
data = {
"code": 200,
"message": "success",
"data": {
"user_id": 12345,
"username": "test_user"
}
}
# 提取 user_id
jsonpath_expr = parse("$.data.user_id")
matches = [match.value for match in jsonpath_expr.find(data)]
user_id = matches[0] if matches else None
assert user_id == 12345
print(f"提取的用户 ID: {user_id}")
4.2 创建辅助函数
为了更方便地在测试中使用 JSONPath,我们可以创建一个辅助函数:
import pytest
from jsonpath_ng import parse
from typing import Any, List, Optional
def extract_jsonpath(data: dict, jsonpath_str: str, default: Any = None) -> Any:
"""
从 JSON 数据中提取值
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
default: 如果未找到时的默认值
返回:
提取的值,如果未找到则返回 default
"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
if matches:
# 如果只有一个结果,直接返回;如果有多个,返回列表
return matches[0] if len(matches) == 1 else matches
return default
except Exception as e:
print(f"JSONPath 提取失败: {e}")
return default
def extract_jsonpath_list(data: dict, jsonpath_str: str) -> List[Any]:
"""
从 JSON 数据中提取多个值(总是返回列表)
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
返回:
提取的值列表
"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches
except Exception as e:
print(f"JSONPath 提取失败: {e}")
return []
# 使用辅助函数
def test_with_helper_function():
"""使用辅助函数提取数据"""
data = {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
}
# 提取单个值
first_user_name = extract_jsonpath(data, "$.users[0].name")
assert first_user_name == "Alice"
# 提取多个值
all_names = extract_jsonpath_list(data, "$.users[*].name")
assert all_names == ["Alice", "Bob"]
4.3 在 Fixture 中使用 JSONPath
我们可以创建 fixture 来封装 JSONPath 提取逻辑:
import pytest
from jsonpath_ng import parse
import requests
@pytest.fixture
def api_response():
"""模拟 API 响应"""
return {
"code": 200,
"message": "success",
"data": {
"token": "abc123xyz789",
"user": {
"id": 12345,
"username": "test_user",
"email": "test@example.com"
},
"expires_in": 3600
}
}
@pytest.fixture
def auth_token(api_response):
"""从 API 响应中提取 token"""
jsonpath_expr = parse("$.data.token")
matches = [match.value for match in jsonpath_expr.find(api_response)]
return matches[0] if matches else None
@pytest.fixture
def user_id(api_response):
"""从 API 响应中提取用户 ID"""
jsonpath_expr = parse("$.data.user.id")
matches = [match.value for match in jsonpath_expr.find(api_response)]
return matches[0] if matches else None
@pytest.fixture
def user_email(api_response):
"""从 API 响应中提取用户邮箱"""
jsonpath_expr = parse("$.data.user.email")
matches = [match.value for match in jsonpath_expr.find(api_response)]
return matches[0] if matches else None
def test_use_extracted_values(auth_token, user_id, user_email):
"""使用提取的值进行测试"""
assert auth_token == "abc123xyz789"
assert user_id == 12345
assert user_email == "test@example.com"
print(f"Token: {auth_token}")
print(f"User ID: {user_id}")
print(f"Email: {user_email}")
4.4 从真实 API 响应中提取数据
在实际的 API 测试中,我们可以这样使用 JSONPath:
import pytest
from jsonpath_ng import parse
import requests
@pytest.fixture
def login_response():
"""登录接口响应"""
url = "https://api.example.com/login"
data = {
"username": "test_user",
"password": "123456"
}
response = requests.post(url, json=data)
return response.json()
@pytest.fixture
def auth_token(login_response):
"""从登录响应中提取 token"""
jsonpath_expr = parse("$.data.token")
matches = [match.value for match in jsonpath_expr.find(login_response)]
token = matches[0] if matches else None
assert token is not None, "Token 提取失败"
return token
@pytest.fixture
def user_info(login_response):
"""从登录响应中提取用户信息"""
jsonpath_expr = parse("$.data.user")
matches = [match.value for match in jsonpath_expr.find(login_response)]
return matches[0] if matches else {}
def test_get_user_profile(auth_token, user_info):
"""使用提取的 token 获取用户资料"""
# 从 user_info 中提取用户 ID
user_id = user_info.get("id")
# 使用 token 发送请求
url = f"https://api.example.com/users/{user_id}"
headers = {"Authorization": f"Bearer {auth_token}"}
response = requests.get(url, headers=headers)
assert response.status_code == 200
profile_data = response.json()
# 使用 JSONPath 验证响应数据
jsonpath_expr = parse("$.data.username")
matches = [match.value for match in jsonpath_expr.find(profile_data)]
username = matches[0] if matches else None
assert username == "test_user"
5. JSONPath 高级用法
5.1 过滤表达式
JSONPath 支持使用过滤表达式来筛选数据:
from jsonpath_ng import parse
data = {
"users": [
{"id": 1, "name": "Alice", "age": 30, "active": True},
{"id": 2, "name": "Bob", "age": 25, "active": False},
{"id": 3, "name": "Charlie", "age": 35, "active": True},
{"id": 4, "name": "David", "age": 28, "active": True}
]
}
# 注意:jsonpath-ng 的过滤语法可能与其他实现不同
# 这里展示基本用法,实际使用时请参考 jsonpath-ng 文档
# 提取所有活跃用户的姓名
# 注意:jsonpath-ng 的过滤语法需要使用特定的方式
# 这里我们先用 Python 代码演示概念
# 提取所有年龄大于 28 的用户
# 在实际项目中,可能需要结合 Python 代码来实现复杂过滤
5.2 处理数组数据
JSONPath 在处理数组数据时非常强大:
from jsonpath_ng import parse
data = {
"orders": [
{
"id": 1001,
"items": [
{"product_id": 1, "quantity": 2, "price": 10.0},
{"product_id": 2, "quantity": 1, "price": 20.0}
],
"total": 40.0
},
{
"id": 1002,
"items": [
{"product_id": 3, "quantity": 3, "price": 15.0}
],
"total": 45.0
}
]
}
# 提取所有订单 ID
jsonpath_expr = parse("$.orders[*].id")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"所有订单 ID: {matches}") # 输出: [1001, 1002]
# 提取所有订单的总金额
jsonpath_expr = parse("$.orders[*].total")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"所有订单总金额: {matches}") # 输出: [40.0, 45.0]
# 提取第一个订单的所有商品 ID
jsonpath_expr = parse("$.orders[0].items[*].product_id")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"第一个订单的商品 ID: {matches}") # 输出: [1, 2]
# 提取所有订单的所有商品 ID(嵌套数组)
jsonpath_expr = parse("$.orders[*].items[*].product_id")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"所有商品 ID: {matches}") # 输出: [1, 2, 3]
5.3 处理嵌套结构
JSONPath 可以轻松处理深层嵌套的 JSON 结构:
from jsonpath_ng import parse
data = {
"response": {
"status": {
"code": 200,
"message": "OK"
},
"payload": {
"user": {
"profile": {
"personal": {
"name": "Alice",
"age": 30
},
"contact": {
"email": "alice@example.com",
"phone": "123-456-7890"
}
}
}
}
}
}
# 传统方式:需要多层嵌套访问
name = data["response"]["payload"]["user"]["profile"]["personal"]["name"]
print(f"传统方式: {name}") # 输出: Alice
# JSONPath 方式:简洁明了
jsonpath_expr = parse("$.response.payload.user.profile.personal.name")
matches = [match.value for match in jsonpath_expr.find(data)]
name = matches[0] if matches else None
print(f"JSONPath 方式: {name}") # 输出: Alice
# 使用递归下降操作符查找所有 email 字段
jsonpath_expr = parse("$..email")
matches = [match.value for match in jsonpath_expr.find(data)]
print(f"递归查找 email: {matches}") # 输出: ['alice@example.com']
5.4 处理可选字段
在实际 API 测试中,某些字段可能不存在。JSONPath 可以帮助我们安全地提取这些字段:
from jsonpath_ng import parse
def safe_extract(data: dict, jsonpath_str: str, default: Any = None) -> Any:
"""安全地提取 JSONPath 值,处理字段不存在的情况"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches[0] if matches else default
except Exception:
return default
# 测试数据:有些用户有 email,有些没有
data = {
"users": [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob"}, # 没有 email 字段
{"id": 3, "name": "Charlie", "email": "charlie@example.com"}
]
}
# 安全地提取所有用户的 email(不存在的返回 None)
for i in range(len(data["users"])):
email = safe_extract(data, f"$.users[{i}].email", default="无邮箱")
print(f"用户 {i+1} 的邮箱: {email}")
# 输出:
# 用户 1 的邮箱: alice@example.com
# 用户 2 的邮箱: 无邮箱
# 用户 3 的邮箱: charlie@example.com
6. 实际应用场景
6.1 场景一:API 测试中的 Token 提取
import pytest
from jsonpath_ng import parse
import requests
class TestAPIAuthentication:
"""API 认证测试"""
@pytest.fixture
def login_response(self):
"""登录接口响应"""
url = "https://api.example.com/login"
data = {"username": "test_user", "password": "123456"}
response = requests.post(url, json=data)
return response.json()
@pytest.fixture
def auth_token(self, login_response):
"""从登录响应中提取 token"""
jsonpath_expr = parse("$.data.token")
matches = [match.value for match in jsonpath_expr.find(login_response)]
token = matches[0] if matches else None
assert token is not None, "Token 提取失败"
assert len(token) > 0, "Token 不能为空"
return token
@pytest.fixture
def refresh_token(self, login_response):
"""从登录响应中提取刷新 token"""
jsonpath_expr = parse("$.data.refresh_token")
matches = [match.value for match in jsonpath_expr.find(login_response)]
return matches[0] if matches else None
@pytest.fixture
def token_expires_in(self, login_response):
"""从登录响应中提取 token 过期时间"""
jsonpath_expr = parse("$.data.expires_in")
matches = [match.value for match in jsonpath_expr.find(login_response)]
return matches[0] if matches else None
def test_token_extraction(self, auth_token, refresh_token, token_expires_in):
"""测试 token 提取"""
print(f"Auth Token: {auth_token}")
print(f"Refresh Token: {refresh_token}")
print(f"Expires In: {token_expires_in} 秒")
assert auth_token is not None
if refresh_token:
assert refresh_token is not None
if token_expires_in:
assert token_expires_in > 0
def test_use_token_for_api_call(self, auth_token):
"""使用提取的 token 调用受保护的 API"""
url = "https://api.example.com/protected"
headers = {"Authorization": f"Bearer {auth_token}"}
response = requests.get(url, headers=headers)
assert response.status_code == 200
# 使用 JSONPath 验证响应
response_data = response.json()
jsonpath_expr = parse("$.data.user_id")
matches = [match.value for match in jsonpath_expr.find(response_data)]
user_id = matches[0] if matches else None
assert user_id is not None
print(f"用户 ID: {user_id}")
6.2 场景二:提取列表数据并验证
import pytest
from jsonpath_ng import parse
import requests
class TestUserList:
"""用户列表测试"""
@pytest.fixture
def users_response(self):
"""获取用户列表响应"""
url = "https://api.example.com/users"
response = requests.get(url)
return response.json()
@pytest.fixture
def user_list(self, users_response):
"""提取用户列表"""
jsonpath_expr = parse("$.data.users[*]")
matches = [match.value for match in jsonpath_expr.find(users_response)]
return matches
@pytest.fixture
def user_ids(self, users_response):
"""提取所有用户 ID"""
jsonpath_expr = parse("$.data.users[*].id")
matches = [match.value for match in jsonpath_expr.find(users_response)]
return matches
@pytest.fixture
def user_names(self, users_response):
"""提取所有用户名"""
jsonpath_expr = parse("$.data.users[*].name")
matches = [match.value for match in jsonpath_expr.find(users_response)]
return matches
@pytest.fixture
def total_count(self, users_response):
"""提取总数"""
jsonpath_expr = parse("$.data.total")
matches = [match.value for match in jsonpath_expr.find(users_response)]
return matches[0] if matches else 0
def test_user_list_structure(self, user_list, total_count):
"""验证用户列表结构"""
assert isinstance(user_list, list)
assert len(user_list) == total_count
# 验证每个用户都有必需的字段
for user in user_list:
assert "id" in user
assert "name" in user
assert "email" in user
def test_user_ids_unique(self, user_ids):
"""验证用户 ID 唯一性"""
assert len(user_ids) == len(set(user_ids)), "用户 ID 不唯一"
print(f"用户 ID 列表: {user_ids}")
def test_user_names_not_empty(self, user_names):
"""验证用户名不为空"""
for name in user_names:
assert name is not None
assert len(name.strip()) > 0
print(f"用户名列表: {user_names}")
def test_first_user_details(self, users_response):
"""验证第一个用户的详细信息"""
# 提取第一个用户的所有信息
jsonpath_expr = parse("$.data.users[0]")
matches = [match.value for match in jsonpath_expr.find(users_response)]
first_user = matches[0] if matches else {}
assert first_user.get("id") is not None
assert first_user.get("name") is not None
assert first_user.get("email") is not None
print(f"第一个用户: {first_user}")
6.3 场景三:订单流程测试
import pytest
from jsonpath_ng import parse
import requests
class TestOrderFlow:
"""订单流程测试"""
@pytest.fixture
def create_order_response(self, auth_token):
"""创建订单响应"""
url = "https://api.example.com/orders"
headers = {"Authorization": f"Bearer {auth_token}"}
data = {
"product_id": 1001,
"quantity": 2,
"address": "123 Main St"
}
response = requests.post(url, json=data, headers=headers)
return response.json()
@pytest.fixture
def order_id(self, create_order_response):
"""提取订单 ID"""
jsonpath_expr = parse("$.data.order_id")
matches = [match.value for match in jsonpath_expr.find(create_order_response)]
return matches[0] if matches else None
@pytest.fixture
def order_total(self, create_order_response):
"""提取订单总金额"""
jsonpath_expr = parse("$.data.total")
matches = [match.value for match in jsonpath_expr.find(create_order_response)]
return matches[0] if matches else None
@pytest.fixture
def order_items(self, create_order_response):
"""提取订单商品列表"""
jsonpath_expr = parse("$.data.items[*]")
matches = [match.value for match in jsonpath_expr.find(create_order_response)]
return matches
@pytest.fixture
def order_status(self, create_order_response):
"""提取订单状态"""
jsonpath_expr = parse("$.data.status")
matches = [match.value for match in jsonpath_expr.find(create_order_response)]
return matches[0] if matches else None
def test_order_creation(self, order_id, order_total, order_status):
"""验证订单创建"""
assert order_id is not None
assert order_total > 0
assert order_status == "pending"
print(f"订单 ID: {order_id}")
print(f"订单总金额: {order_total}")
print(f"订单状态: {order_status}")
def test_order_items(self, order_items):
"""验证订单商品"""
assert len(order_items) > 0
# 提取所有商品 ID
product_ids = [item.get("product_id") for item in order_items]
print(f"商品 ID 列表: {product_ids}")
# 提取所有商品数量
quantities = [item.get("quantity") for item in order_items]
print(f"商品数量列表: {quantities}")
# 验证每个商品都有必需字段
for item in order_items:
assert "product_id" in item
assert "quantity" in item
assert "price" in item
def test_get_order_details(self, order_id, auth_token):
"""获取订单详情并验证"""
url = f"https://api.example.com/orders/{order_id}"
headers = {"Authorization": f"Bearer {auth_token}"}
response = requests.get(url, headers=headers)
assert response.status_code == 200
order_data = response.json()
# 使用 JSONPath 提取订单详情
jsonpath_expr = parse("$.data.order_id")
matches = [match.value for match in jsonpath_expr.find(order_data)]
retrieved_order_id = matches[0] if matches else None
assert retrieved_order_id == order_id
# 提取订单创建时间
jsonpath_expr = parse("$.data.created_at")
matches = [match.value for match in jsonpath_expr.find(order_data)]
created_at = matches[0] if matches else None
assert created_at is not None
print(f"订单创建时间: {created_at}")
6.4 场景四:数据验证和断言
import pytest
from jsonpath_ng import parse
class TestDataValidation:
"""数据验证测试"""
@pytest.fixture
def api_response(self):
"""模拟 API 响应"""
return {
"code": 200,
"message": "success",
"data": {
"users": [
{
"id": 1,
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"active": True
},
{
"id": 2,
"name": "Bob",
"age": 25,
"email": "bob@example.com",
"active": False
}
],
"total": 2,
"page": 1,
"page_size": 10
}
}
def test_validate_response_structure(self, api_response):
"""验证响应结构"""
# 验证 code 字段
jsonpath_expr = parse("$.code")
matches = [match.value for match in jsonpath_expr.find(api_response)]
code = matches[0] if matches else None
assert code == 200
# 验证 message 字段
jsonpath_expr = parse("$.message")
matches = [match.value for match in jsonpath_expr.find(api_response)]
message = matches[0] if matches else None
assert message == "success"
# 验证 data 字段存在
jsonpath_expr = parse("$.data")
matches = [match.value for match in jsonpath_expr.find(api_response)]
assert len(matches) > 0
def test_validate_user_data(self, api_response):
"""验证用户数据"""
# 提取所有用户
jsonpath_expr = parse("$.data.users[*]")
matches = [match.value for match in jsonpath_expr.find(api_response)]
users = matches
assert len(users) == 2
# 验证每个用户都有必需的字段
required_fields = ["id", "name", "age", "email", "active"]
for user in users:
for field in required_fields:
assert field in user, f"用户缺少字段: {field}"
def test_validate_user_ids(self, api_response):
"""验证用户 ID"""
# 提取所有用户 ID
jsonpath_expr = parse("$.data.users[*].id")
matches = [match.value for match in jsonpath_expr.find(api_response)]
user_ids = matches
# 验证 ID 都是正整数
for user_id in user_ids:
assert isinstance(user_id, int)
assert user_id > 0
# 验证 ID 唯一
assert len(user_ids) == len(set(user_ids))
def test_validate_user_emails(self, api_response):
"""验证用户邮箱格式"""
import re
# 提取所有邮箱
jsonpath_expr = parse("$.data.users[*].email")
matches = [match.value for match in jsonpath_expr.find(api_response)]
emails = matches
# 验证邮箱格式
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$'
for email in emails:
assert re.match(email_pattern, email), f"邮箱格式不正确: {email}"
def test_validate_pagination(self, api_response):
"""验证分页信息"""
# 提取分页字段
total = extract_jsonpath(api_response, "$.data.total")
page = extract_jsonpath(api_response, "$.data.page")
page_size = extract_jsonpath(api_response, "$.data.page_size")
assert total >= 0
assert page > 0
assert page_size > 0
# 验证用户数量与 total 一致
jsonpath_expr = parse("$.data.users[*]")
matches = [match.value for match in jsonpath_expr.find(api_response)]
user_count = len(matches)
assert user_count == total
7. 创建 JSONPath 工具类
为了更方便地在项目中使用 JSONPath,我们可以创建一个工具类:
# jsonpath_utils.py
from jsonpath_ng import parse
from typing import Any, List, Optional, Dict
import json
class JSONPathExtractor:
"""JSONPath 提取工具类"""
@staticmethod
def extract(data: Dict, jsonpath_str: str, default: Any = None) -> Any:
"""
从 JSON 数据中提取单个值
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
default: 如果未找到时的默认值
返回:
提取的值,如果未找到则返回 default
"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
if matches:
return matches[0] if len(matches) == 1 else matches
return default
except Exception as e:
print(f"JSONPath 提取失败: {e}")
return default
@staticmethod
def extract_all(data: Dict, jsonpath_str: str) -> List[Any]:
"""
从 JSON 数据中提取所有匹配的值(总是返回列表)
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
返回:
提取的值列表
"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches
except Exception as e:
print(f"JSONPath 提取失败: {e}")
return []
@staticmethod
def extract_first(data: Dict, jsonpath_str: str, default: Any = None) -> Any:
"""
从 JSON 数据中提取第一个匹配的值
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
default: 如果未找到时的默认值
返回:
第一个匹配的值,如果未找到则返回 default
"""
matches = JSONPathExtractor.extract_all(data, jsonpath_str)
return matches[0] if matches else default
@staticmethod
def extract_last(data: Dict, jsonpath_str: str, default: Any = None) -> Any:
"""
从 JSON 数据中提取最后一个匹配的值
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
default: 如果未找到时的默认值
返回:
最后一个匹配的值,如果未找到则返回 default
"""
matches = JSONPathExtractor.extract_all(data, jsonpath_str)
return matches[-1] if matches else default
@staticmethod
def exists(data: Dict, jsonpath_str: str) -> bool:
"""
检查 JSONPath 表达式是否匹配到数据
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
返回:
如果匹配到数据返回 True,否则返回 False
"""
matches = JSONPathExtractor.extract_all(data, jsonpath_str)
return len(matches) > 0
@staticmethod
def count(data: Dict, jsonpath_str: str) -> int:
"""
统计 JSONPath 表达式匹配到的数据数量
参数:
data: JSON 数据(字典)
jsonpath_str: JSONPath 表达式
返回:
匹配到的数据数量
"""
matches = JSONPathExtractor.extract_all(data, jsonpath_str)
return len(matches)
# 使用示例
if __name__ == "__main__":
data = {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
}
# 提取单个值
first_name = JSONPathExtractor.extract(data, "$.users[0].name")
print(f"第一个用户名: {first_name}")
# 提取所有值
all_names = JSONPathExtractor.extract_all(data, "$.users[*].name")
print(f"所有用户名: {all_names}")
# 检查是否存在
exists = JSONPathExtractor.exists(data, "$.users[*].name")
print(f"是否存在用户名: {exists}")
# 统计数量
count = JSONPathExtractor.count(data, "$.users[*]")
print(f"用户数量: {count}")
在 pytest 中使用工具类:
import pytest
from jsonpath_utils import JSONPathExtractor
class TestWithJSONPathUtils:
"""使用 JSONPath 工具类的测试"""
@pytest.fixture
def api_response(self):
return {
"code": 200,
"data": {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
}
}
def test_extract_with_utils(self, api_response):
"""使用工具类提取数据"""
# 提取单个值
first_name = JSONPathExtractor.extract(api_response, "$.data.users[0].name")
assert first_name == "Alice"
# 提取所有值
all_names = JSONPathExtractor.extract_all(api_response, "$.data.users[*].name")
assert all_names == ["Alice", "Bob"]
# 检查是否存在
exists = JSONPathExtractor.exists(api_response, "$.data.users[*].name")
assert exists is True
# 统计数量
count = JSONPathExtractor.count(api_response, "$.data.users[*]")
assert count == 2
8. 在 conftest.py 中配置 JSONPath
我们可以在 conftest.py 中配置 JSONPath 相关的 fixture,让所有测试文件都能使用:
# conftest.py
import pytest
from jsonpath_ng import parse
from typing import Any, Dict
@pytest.fixture
def jsonpath_extractor():
"""JSONPath 提取器 fixture"""
class Extractor:
@staticmethod
def extract(data: Dict, jsonpath_str: str, default: Any = None) -> Any:
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
if matches:
return matches[0] if len(matches) == 1 else matches
return default
except Exception:
return default
@staticmethod
def extract_all(data: Dict, jsonpath_str: str) -> list:
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches
except Exception:
return []
return Extractor
# 使用示例
def test_with_jsonpath_fixture(jsonpath_extractor):
"""使用 JSONPath fixture"""
data = {
"users": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
}
# 使用 fixture 提取数据
first_name = jsonpath_extractor.extract(data, "$.users[0].name")
assert first_name == "Alice"
all_names = jsonpath_extractor.extract_all(data, "$.users[*].name")
assert all_names == ["Alice", "Bob"]
9. 常见问题和解决方案
9.1 问题一:JSONPath 表达式语法错误
问题描述:JSONPath 表达式写错了,导致提取失败。
解决方案:仔细检查 JSONPath 语法,使用正确的操作符。
from jsonpath_ng import parse
data = {"user": {"name": "Alice"}}
# ❌ 错误:缺少 $ 符号
# jsonpath_expr = parse("user.name") # 这会报错
# ✅ 正确:以 $ 开头
jsonpath_expr = parse("$.user.name")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['Alice']
9.2 问题二:字段不存在导致 KeyError
问题描述:JSON 数据中某些字段可能不存在,直接访问会报错。
解决方案:使用安全提取函数,提供默认值。
def safe_extract(data: dict, jsonpath_str: str, default: Any = None) -> Any:
"""安全提取,处理字段不存在的情况"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches[0] if matches else default
except Exception:
return default
data = {"user": {"name": "Alice"}} # 没有 email 字段
# 安全提取,字段不存在时返回默认值
email = safe_extract(data, "$.user.email", default="无邮箱")
print(email) # 输出: 无邮箱
9.3 问题三:提取数组数据时返回单个值还是列表
问题描述:不确定 JSONPath 会返回单个值还是列表。
解决方案:明确使用 extract_all 或 extract_first 方法。
from jsonpath_ng import parse
data = {
"users": [
{"name": "Alice"},
{"name": "Bob"}
]
}
# 提取单个元素 - 返回单个值
jsonpath_expr = parse("$.users[0].name")
matches = [match.value for match in jsonpath_expr.find(data)]
first_name = matches[0] if matches else None
print(f"第一个用户名: {first_name}") # 输出: Alice
# 提取所有元素 - 返回列表
jsonpath_expr = parse("$.users[*].name")
matches = [match.value for match in jsonpath_expr.find(data)]
all_names = matches
print(f"所有用户名: {all_names}") # 输出: ['Alice', 'Bob']
9.4 问题四:处理嵌套数组数据
问题描述:需要从嵌套的数组中提取数据。
解决方案:使用递归下降操作符或正确的路径。
from jsonpath_ng import parse
data = {
"orders": [
{
"items": [
{"product_id": 1, "name": "Product 1"},
{"product_id": 2, "name": "Product 2"}
]
},
{
"items": [
{"product_id": 3, "name": "Product 3"}
]
}
]
}
# 提取所有订单的所有商品名称
jsonpath_expr = parse("$.orders[*].items[*].name")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['Product 1', 'Product 2', 'Product 3']
# 使用递归下降操作符
jsonpath_expr = parse("$..name")
matches = [match.value for match in jsonpath_expr.find(data)]
print(matches) # 输出: ['Product 1', 'Product 2', 'Product 3']
9.5 问题五:性能问题
问题描述:在大量数据中使用 JSONPath 可能影响性能。
解决方案:缓存编译后的 JSONPath 表达式,避免重复编译。
from jsonpath_ng import parse
from functools import lru_cache
# 缓存编译后的 JSONPath 表达式
@lru_cache(maxsize=100)
def get_compiled_jsonpath(jsonpath_str: str):
"""获取编译后的 JSONPath 表达式(带缓存)"""
return parse(jsonpath_str)
def extract_with_cache(data: dict, jsonpath_str: str) -> list:
"""使用缓存的 JSONPath 表达式提取数据"""
jsonpath_expr = get_compiled_jsonpath(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches
# 使用示例
data = {"users": [{"name": "Alice"}, {"name": "Bob"}]}
# 第一次调用会编译表达式
result1 = extract_with_cache(data, "$.users[*].name")
# 第二次调用使用缓存的表达式(更快)
result2 = extract_with_cache(data, "$.users[*].name")
10. 最佳实践
10.1 使用有意义的变量名
# ❌ 不好的做法
jsonpath_expr = parse("$.data.users[0].id")
matches = [match.value for match in jsonpath_expr.find(data)]
id = matches[0]
# ✅ 好的做法
user_id_jsonpath = "$.data.users[0].id"
jsonpath_expr = parse(user_id_jsonpath)
matches = [match.value for match in jsonpath_expr.find(data)]
user_id = matches[0] if matches else None
10.2 创建辅助函数
# ✅ 创建可重用的辅助函数
def extract_user_id(response_data: dict) -> Optional[int]:
"""从响应中提取用户 ID"""
jsonpath_expr = parse("$.data.user.id")
matches = [match.value for match in jsonpath_expr.find(response_data)]
return matches[0] if matches else None
# 在测试中使用
def test_user_id(api_response):
user_id = extract_user_id(api_response)
assert user_id is not None
10.3 添加错误处理
def safe_extract_jsonpath(data: dict, jsonpath_str: str, default: Any = None) -> Any:
"""安全提取 JSONPath,包含完整的错误处理"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
if matches:
return matches[0] if len(matches) == 1 else matches
return default
except Exception as e:
print(f"JSONPath 提取失败: {jsonpath_str}, 错误: {e}")
return default
10.4 使用类型提示
from typing import Optional, List, Dict, Any
def extract_jsonpath(
data: Dict[str, Any],
jsonpath_str: str,
default: Any = None
) -> Optional[Any]:
"""提取 JSONPath 值,带类型提示"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches[0] if matches else default
except Exception:
return default
10.5 文档化 JSONPath 表达式
# ✅ 在代码中注释说明 JSONPath 表达式的含义
def extract_auth_token(login_response: dict) -> Optional[str]:
"""
从登录响应中提取认证 token
参数:
login_response: 登录接口的响应数据
返回:
认证 token,如果不存在则返回 None
JSONPath 表达式说明:
$.data.token - 从响应数据的 data 字段中提取 token
"""
jsonpath_str = "$.data.token" # 提取路径:response -> data -> token
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(login_response)]
return matches[0] if matches else None
11. 完整示例:API 测试项目
让我们创建一个完整的 API 测试项目示例,展示如何在实际项目中使用 JSONPath:
# test_api_with_jsonpath.py
import pytest
from jsonpath_ng import parse
from typing import Optional, List, Dict, Any
import requests
class JSONPathHelper:
"""JSONPath 辅助类"""
@staticmethod
def extract(data: Dict, jsonpath_str: str, default: Any = None) -> Any:
"""提取单个值"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches[0] if matches else default
except Exception:
return default
@staticmethod
def extract_all(data: Dict, jsonpath_str: str) -> List[Any]:
"""提取所有匹配的值"""
try:
jsonpath_expr = parse(jsonpath_str)
matches = [match.value for match in jsonpath_expr.find(data)]
return matches
except Exception:
return []
class TestUserAPI:
"""用户 API 测试"""
BASE_URL = "https://api.example.com"
@pytest.fixture
def login_response(self):
"""登录并获取响应"""
url = f"{self.BASE_URL}/login"
data = {"username": "test_user", "password": "123456"}
response = requests.post(url, json=data)
return response.json()
@pytest.fixture
def auth_token(self, login_response):
"""从登录响应中提取 token"""
token = JSONPathHelper.extract(login_response, "$.data.token")
assert token is not None, "Token 提取失败"
return token
@pytest.fixture
def user_id(self, login_response):
"""从登录响应中提取用户 ID"""
user_id = JSONPathHelper.extract(login_response, "$.data.user.id")
assert user_id is not None, "用户 ID 提取失败"
return user_id
def test_login_success(self, login_response):
"""测试登录成功"""
# 验证响应码
code = JSONPathHelper.extract(login_response, "$.code")
assert code == 200
# 验证消息
message = JSONPathHelper.extract(login_response, "$.message")
assert message == "success"
# 验证 token 存在
token = JSONPathHelper.extract(login_response, "$.data.token")
assert token is not None
assert len(token) > 0
def test_get_user_list(self, auth_token):
"""测试获取用户列表"""
url = f"{self.BASE_URL}/users"
headers = {"Authorization": f"Bearer {auth_token}"}
response = requests.get(url, headers=headers)
assert response.status_code == 200
response_data = response.json()
# 提取用户列表
users = JSONPathHelper.extract_all(response_data, "$.data.users[*]")
assert len(users) > 0
# 提取所有用户 ID
user_ids = JSONPathHelper.extract_all(response_data, "$.data.users[*].id")
assert len(user_ids) == len(users)
# 验证用户 ID 唯一
assert len(user_ids) == len(set(user_ids))
# 提取所有用户名
user_names = JSONPathHelper.extract_all(response_data, "$.data.users[*].name")
assert len(user_names) == len(users)
# 验证每个用户名都不为空
for name in user_names:
assert name is not None
assert len(name.strip()) > 0
def test_get_user_detail(self, auth_token, user_id):
"""测试获取用户详情"""
url = f"{self.BASE_URL}/users/{user_id}"
headers = {"Authorization": f"Bearer {auth_token}"}
response = requests.get(url, headers=headers)
assert response.status_code == 200
response_data = response.json()
# 验证用户 ID 匹配
retrieved_user_id = JSONPathHelper.extract(response_data, "$.data.id")
assert retrieved_user_id == user_id
# 验证用户信息完整性
username = JSONPathHelper.extract(response_data, "$.data.username")
email = JSONPathHelper.extract(response_data, "$.data.email")
assert username is not None
assert email is not None
assert "@" in email
class TestOrderAPI:
"""订单 API 测试"""
BASE_URL = "https://api.example.com"
@pytest.fixture
def auth_token(self, login_response):
"""认证 token"""
return JSONPathHelper.extract(login_response, "$.data.token")
@pytest.fixture
def create_order_response(self, auth_token):
"""创建订单响应"""
url = f"{self.BASE_URL}/orders"
headers = {"Authorization": f"Bearer {auth_token}"}
data = {
"product_id": 1001,
"quantity": 2
}
response = requests.post(url, json=data, headers=headers)
return response.json()
@pytest.fixture
def order_id(self, create_order_response):
"""订单 ID"""
return JSONPathHelper.extract(create_order_response, "$.data.order_id")
def test_create_order(self, create_order_response, order_id):
"""测试创建订单"""
# 验证订单创建成功
code = JSONPathHelper.extract(create_order_response, "$.code")
assert code == 201
# 验证订单 ID 存在
assert order_id is not None
# 提取订单总金额
total = JSONPathHelper.extract(create_order_response, "$.data.total")
assert total > 0
# 提取订单状态
status = JSONPathHelper.extract(create_order_response, "$.data.status")
assert status == "pending"
# 提取订单商品列表
items = JSONPathHelper.extract_all(create_order_response, "$.data.items[*]")
assert len(items) > 0
# 提取所有商品 ID
product_ids = JSONPathHelper.extract_all(
create_order_response,
"$.data.items[*].product_id"
)
assert len(product_ids) == len(items)
def test_get_order_detail(self, auth_token, order_id):
"""测试获取订单详情"""
url = f"{self.BASE_URL}/orders/{order_id}"
headers = {"Authorization": f"Bearer {auth_token}"}
response = requests.get(url, headers=headers)
assert response.status_code == 200
response_data = response.json()
# 验证订单 ID 匹配
retrieved_order_id = JSONPathHelper.extract(response_data, "$.data.order_id")
assert retrieved_order_id == order_id
# 提取订单创建时间
created_at = JSONPathHelper.extract(response_data, "$.data.created_at")
assert created_at is not None
# 提取订单更新时间
updated_at = JSONPathHelper.extract(response_data, "$.data.updated_at")
assert updated_at is not None
12. 总结
12.1 JSONPath 的核心优势
- 简洁直观:使用类似文件路径的语法,易于理解和编写
- 功能强大:支持通配符、切片、递归等高级功能
- 灵活性强:可以处理复杂的嵌套结构和数组数据
- 可维护性好:将数据提取逻辑与业务逻辑分离
12.2 在 Pytest 中使用 JSONPath 的要点
- 安装库:使用
jsonpath-ng库 - 创建辅助函数:封装常用的提取逻辑
- 使用 Fixture:在 fixture 中提取数据,便于复用
- 错误处理:始终考虑字段不存在的情况
- 类型提示:使用类型提示提高代码可读性
12.3 学习路径建议
- 第一步:理解 JSONPath 基本语法和操作符
- 第二步:学习在 Python 中使用
jsonpath-ng库 - 第三步:创建辅助函数和工具类
- 第四步:在 pytest fixture 中应用 JSONPath
- 第五步:在实际项目中实践和优化
12.4 实践建议
- 多练习:通过实际项目练习 JSONPath 的使用
- 多思考:思考如何让数据提取更加清晰和可维护
- 多总结:总结不同场景下的最佳实践
- 多参考:参考 JSONPath 官方文档和示例
希望这份详细的教程能够帮助你掌握 pytest 中的 JSONPath 使用!记住,实践是最好的老师,多写代码,多测试,你一定会越来越熟练的!
附录:JSONPath 表达式速查表
A.1 基本操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$ |
根节点 | $ |
. |
子节点 | $.user.name |
[] |
数组索引 | $.users[0] |
[*] |
所有元素 | $.users[*] |
.. |
递归下降 | $..name |
[start:end] |
数组切片 | $.users[0:3] |
A.2 常用表达式示例
| 需求 | JSONPath 表达式 | 说明 |
|---|---|---|
| 提取用户 ID | $.data.user.id |
提取单个值 |
| 提取所有用户名 | $.data.users[*].name |
提取数组中的所有值 |
| 提取第一个用户 | $.data.users[0] |
提取数组第一个元素 |
| 提取最后一个用户 | $.data.users[-1] |
提取数组最后一个元素 |
| 提取前三个用户 | $.data.users[0:3] |
数组切片 |
| 递归查找所有 email | $..email |
递归搜索 |
| 提取所有订单的商品 ID | $.orders[*].items[*].product_id |
嵌套数组 |
A.3 常见错误和解决方案
| 错误 | 原因 | 解决方案 |
|---|---|---|
ParseError |
JSONPath 语法错误 | 检查表达式语法,确保以 $ 开头 |
KeyError |
字段不存在 | 使用安全提取函数,提供默认值 |
| 返回空列表 | 路径不匹配 | 检查 JSON 数据结构,确认路径正确 |
| 性能问题 | 重复编译表达式 | 缓存编译后的表达式 |
文档结束