JSONPath 教程
一、背景:JSONPath 是什么
JSONPath 是面向 JSON 的路径查询语言。思路类似 XML 的 XPath:用一条声明式表达式,从 JSON 中定位、筛选并提取节点,而不必手写层层循环。
- 查询是只读的,不修改原文档。
- 结果通常是匹配值组成的列表(即使只有一个,很多引擎也按列表返回)。
- 实现众多(Jayway、JSONPath-Plus 等),细节上存在方言差异。
JSON 基础见 JSON 教程;在线查询可用 JSONPath 工具。
二、为什么会有 JSONPath
业务 JSON 往往很深。硬写 data.a.b[i].c 难读难维护;结构一变就要改代码。JSONPath 把「怎么走、按什么条件选」写成路径,便于在联调工具、测试脚本、配置映射里复用。
// 意图:fiction 类图书的书名 $.store.book[?(@.category == 'fiction')].title
三、应用场景
- 接口联调:从大响应里抽出关键字段核对
- 自动化测试:对返回体做断言
- 日志监控:批量提取 userId、错误码、耗时等
- 配置 / 规则引擎:字段映射与条件路由
- 数据清洗:从嵌套报文抽扁平列表
四、示例数据(语法小节均基于此)
{
"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
}
},
"expensive": 10
}
五、语法说明(每条附小示例)
5.1 根节点 $
表示文档根,查询一般从这里开始。
$ → 整个 JSON 文档 $.expensive → 10
5.2 子节点:点号 .key
按对象键逐级向下取。
$.store.bicycle.color → "red" $.store.bicycle.price → 19.95
5.3 子节点:方括号 ['key']
与点号等价,更适合键名含 .、-、空格的情况。
$['store']['bicycle']['color'] → "red" // 若键名是 user-id,应写: // $['user-id']
5.4 数组下标 [n]
下标从 0 开始。部分实现支持 [-1] 表示最后一项。
$.store.book[0].title → "Sayings of the Century" $.store.book[2].author → "Herman Melville"
5.5 多选 [n,m]
一次取多个下标。
$.store.book[0,2].title → [ "Sayings of the Century", "Moby Dick" ]
5.6 切片 [start:end]
取下标区间。是否包含终点、是否支持步长,因实现而异。
$.store.book[0:2].title → 前两本书的 title(具体是否含终点请以所用引擎为准)
5.7 通配 *
匹配某一层的全部子节点或数组全部元素。
$.store.book[*].author → [ "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien" ] $.store.* → store 下的 book 数组与 bicycle 对象
5.8 递归下降 ..
任意深度查找字段。范围大、可能更慢,层级明确时优先写死路径。
$..author → 所有 author $..price → [8.95, 12.99, 8.99, 22.99, 19.95] (书价 + 自行车价格) $.store..price → 仅从 store 子树开始找 price
5.9 当前节点 @
只用在过滤器里,表示「正在判断的那一个元素」。
// @ 就是 book 数组里当前这一本 $.store.book[?(@.price < 10)]
5.10 过滤器 [?()]
按条件筛选数组元素;后面可继续点字段做投影。
$.store.book[?(@.price < 10)] → 价格小于 10 的书(完整对象) $.store.book[?(@.price < 10)].title → ["Sayings of the Century", "Moby Dick"]
5.11 比较运算符
| 运算符 | 含义 |
|---|---|
== |
等于 |
!= |
不等于 |
> |
大于 |
>= |
大于等于 |
< |
小于 |
<= |
小于等于 |
// == $.store.book[?(@.category == 'fiction')].title // != $.store.book[?(@.category != 'fiction')].title → ["Sayings of the Century"] // > / >= $.store.book[?(@.price > 10)].title $.store.book[?(@.price >= 20)].title → ["The Lord of the Rings"] // < / <= $.store.book[?(@.price < 10)].title $.store.book[?(@.price <= 8.99)].title
比较时类型要一致:JSON 里是数字,就不要写成 @.price == '8.95'。
5.12 与根路径比较
过滤器里除了 @,还可以用 $ 引用文档其他位置。
$.store.book[?(@.price > $.expensive)].title → [ "Sword of Honour", "The Lord of the Rings" ]
5.13 存在性判断
字段存在即可命中(具体写法因实现略有差异)。
$.store.book[?(@.isbn)].title → [ "Moby Dick", "The Lord of the Rings" ] $.store.book[?(@.isbn)].isbn → [ "0-553-21311-3", "0-395-19395-8" ]
5.14 逻辑组合(方言)
&& 并且、|| 或者;部分库还支持正则 =~。上线前请在目标引擎验证。
$.store.book[?(@.price < 10 && @.category == 'fiction')].title → ["Moby Dick"] $.store.book[?(@.price < 10 || @.category == 'reference')].title → ["Sayings of the Century", "Moby Dick"]
5.15 路径操作符速查
| 符号 | 含义 |
|---|---|
$ |
根节点 |
@ |
过滤器中的当前节点 |
.key / ['key'] |
子属性 |
.. |
递归下降 |
* |
通配 |
[n] / [n,m] / [start:end] |
下标 / 多选 / 切片 |
[?()] |
过滤 |
== != > >= < <= |
比较(过滤器内) |
六、综合示例
下面把多种语法组合到真实一点的报文里。
6.1 接口联调:订单列表
{
"code": 0,
"message": "ok",
"data": {
"page": 1,
"total": 2,
"list": [
{
"orderId": "O1001",
"status": "PAID",
"amount": 99.5,
"buyer": { "name": "Alice" }
},
{
"orderId": "O1002",
"status": "CREATED",
"amount": 12,
"buyer": { "name": "Bob" }
}
]
}
}
$.code
→ 0
$.data.list[*].orderId
→ ["O1001", "O1002"]
$.data.list[?(@.status == 'PAID')].amount
→ [99.5]
$.data.list[?(@.amount >= 50)].buyer.name
→ ["Alice"]
$.data.list[*].buyer.name
→ ["Alice", "Bob"]
$..orderId
→ ["O1001", "O1002"]
6.2 日志排查
{
"ts": "2026-07-19T08:00:00Z",
"level": "ERROR",
"service": "pay-api",
"traceId": "t-9f3a",
"req": { "path": "/v1/pay", "userId": 10086 },
"err": { "code": "TIMEOUT", "detail": "upstream timeout" }
}
$.traceId → "t-9f3a"
$.req.userId → 10086
$.err.code → "TIMEOUT"
$.level → "ERROR"
6.3 权限角色
{
"user": {
"id": 7,
"name": "gary",
"roles": [
{ "code": "admin", "enabled": true },
{ "code": "editor", "enabled": false },
{ "code": "viewer", "enabled": true }
]
}
}
$.user.roles[?(@.enabled == true)].code
→ ["admin", "viewer"]
$.user.roles[?(@.code == 'admin')].enabled
→ [true]
$.user.roles[?(@.enabled != true)].code
→ ["editor"]
6.4 树形评论
{
"postId": 1,
"comments": [
{
"id": 11,
"text": "顶",
"replies": [
{ "id": 111, "text": "同意" },
{ "id": 112, "text": "补充一点" }
]
},
{
"id": 12,
"text": "求更新",
"replies": []
}
]
}
$.comments[*].text
→ ["顶", "求更新"]
$.comments[*].replies[*].text
→ ["同意", "补充一点"]
$..id
→ [11, 111, 112, 12]
$.comments[0].replies[1].text
→ "补充一点"
6.5 配置开关
{
"app": {
"name": "jsonme",
"features": {
"editor": { "enabled": true, "maxSizeMb": 50 },
"compare": { "enabled": true, "maxSizeMb": 20 },
"legacy": { "enabled": false, "maxSizeMb": 5 }
}
}
}
$.app.name
→ "jsonme"
$.app.features.editor.maxSizeMb
→ 50
$.app.features.*.enabled
→ [true, true, false]
七、实践建议与常见坑
- 先窄后宽:优先明确路径;不稳再用
$..。 - 特殊键名用方括号:
$['user-id']、$['a.b']。 - 结果按列表处理:断言时更稳。
- 比较类型一致:数字别和字符串硬比。
- 空列表 ≠ 语法错:可能是条件未命中。
- 方言要实测:不同库细节不同。
沪公网安备31010502007661号