一个真实的提取诉求
后端返回了一个嵌套很深的订单响应,你想立刻取出『状态为 paid 的所有订单号及其总金额』:
{
"orders": [
{ "id": "o1001", "status": "paid", "total": 128.5 },
{ "id": "o1002", "status": "pending", "total": 59.0 },
{ "id": "o1003", "status": "paid", "items": [
{ "sku": "A-01", "price": 39.9 },
{ "sku": "A-02", "price": 20.1 }
], "total": 60.0 }
]
}
用代码,你得写 for + 判断 + 收集,至少几行;用 JSONPath,一行:
$.orders[?(@.status == "paid")]. {id, total}
这不是标准单条路径可表达的——它需要遍历 + 过滤 + 投影。JSONPath 的设计目标就是让你用一行字符串完成这套操作,尤其适合接口调试、日志清洗和配置提取。
语法速查表
| 表达式 | 含义 | 返回 |
|---|---|---|
$ |
根节点 | 整个文档 |
.key / ['key'] |
子属性 | 该属性 |
* |
通配符 | 所有子属性/元素 |
..key |
递归下降 | 任意层级匹配的 key |
[0] |
数组索引 | 指定元素 |
[0,2] |
多索引 | 第 0、2 个元素 |
[start:end] |
切片 | 区间元素 |
[?(@.k==v)] |
过滤 | 满足条件的元素 |
逐个拆解示例
$ → 整个对象
$.orders[1].id → "o1002"
$.orders[*].id → ["o1001","o1002","o1003"]
$.orders[?(@.total>60)].id
→ ["o1001","o1003"]
$-orders..price → [39.9,20.1] (递归取所有 price)
注意 ..price 把嵌套在 items 里的 price 也递归摘了出来——这正是递归下降的价值。
关键概念:$ 与 @
$永远从根开始,$.a.b.c是明确路径;@只在过滤器里表示『当前元素』;- 两者混用可写『先按条件筛子集,再在子集内取某个字段』。
进阶:表达式与逻辑
$.orders[?(@.status=="paid" && @.total>=60)].id
$..[?(@.status=="paid")] # 任意层级筛 paid
用布尔逻辑组合条件,甚至可以对整个文档递归筛——很多工具站里的 JSONPath 查找器正是靠这套让你把筛选写进一行。
匹配到空怎么办
查询语法本身不校验『键不存在』,常见两种情况:
- 返回
[]:路径合法但没有命中(如过滤条件无匹配对象); - 抛异常:路径写法本身非法(如某段访问了数字下标但没有数组)。
判断一条 JSONPath 是否正确,先格式化 JSON(保证结构可读),再逐步拆路径段,逐层用通配符验证哪一级路径断了。这正是「先 json 格式化、再 JSONPath 查找」的组合使用场景。
实战工作流
在调试一个第三方 API 时,我的取数顺序通常是:
- 先格式化响应,看整体层级(避免在压缩 JSON 里数括号);
- 用
$[*].之类粗通配确认字段名拼写; - 再落到精确路径或过滤表达式取值;
- 反查:拿同样路径去较 JSON 差异,确认两条响应是否一致。
自查
把开头的 orders 例子丢进任意 JSONPath 工具:
$.orders[*].id应返回 3 个 id;$.orders[?(@.status=="paid")].id应返回 2 个(o1001、o1003);$.orders..price应把嵌套的 39.9、20.1 也取出来。
三句全对,说明你对通配、递归、过滤的直觉已经建立。