Fastjson2官方文档中提到“在Fastjson2中,JSONPath是一等公民”,看得出来,JSONPath作为处理JSON 的简洁、灵活、类路径的表达式语言愈发被重点关注。
本文将从语法规范、操作符详解、表达式类型、实现差异、高级用法、常见陷阱 等多个维度详解。
一、JSONPath 基础概念
1.1 根节点:$
- $ 表明 JSON 文档的根对象(或根数组)。
- 所有路径都从 $ 开始。
示例:
ounter(line
{ "name": "Alice" }
- $ → 整个对象 { “name”: “Alice” }
- $.name → “Alice”
1.2 当前节点:@
- 在过滤表达式中,@ 表明当前正在被评估的节点。
- 仅在 ?() 过滤器中有效。
示例:
ounter(line
[10, 20, 30]
- $[?(@ > 15)] → [20, 30]
二、JSONPath 操作符详解
|
操作符 |
名称 |
描述 |
示例 |
|
$ |
根节点 |
表明整个 JSON 文档的起点 |
$.store |
|
@ |
当前节点 |
在过滤表达式中引用当前元素 |
$[?(@.price > 10)] |
|
. |
子属性选择器(点表明法) |
选择对象的直接子属性 |
$.store.book |
|
[] |
子属性/索引选择器(括号表明法) |
支持字符串键、数字索引、表达式 |
$['store']['book'] 或 $[0] |
|
.. |
递归下降(深度优先遍历) |
匹配任意层级下的匹配项 |
$..author |
|
* |
通配符 |
匹配所有字段或数组元素 |
$.store.book[*].author |
|
[n] |
数组索引 |
获取数组第 n 个元素(从 0 开始) |
$.book[0] |
|
[start:end:step] |
切片(Slice) |
类似 Python 的切片语法(部分实现支持) |
$[1:3] 、$[::-1] |
|
[?(expr)] |
过滤表达式 |
对数组元素进行条件筛选 |
$[?(@.price < 10)] |
|
, |
联合选择(Union) |
在 [] 中选择多个键或索引(部分实现支持) |
$['name','age'] 或 $[0,2] |
⚠️ 注意:并非所有实现都支持全部操作符(如切片、联合选择、负索引等)。
三、路径表达式类型详解
3.1 点表明法(Dot-Notation)
- 适用于键名是合法标识符(不含空格、特殊字符)的情况。
- 更简洁。
示例:
ounter(line
{ "user": { "name": "Bob" } }
- $.user.name → “Bob”
3.2 括号表明法(Bracket-Notation)
- 适用于键名包含空格、特殊字符、数字开头,或动态键名。
- 更通用。
示例:
ounter(line
{ "user info": { "1st-name": "Bob" } }
- $['user info']['1st-name'] → “Bob”
✅ 推荐:在不确定键名合法性时,统一使用括号表明法。
3.3 递归下降(..)
- 从当前节点开始,递归搜索所有子层级中匹配的字段。
- 超级强劲,但可能性能开销大。
示例:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
"a": {
"b": {
"name": "X"
}
},
"c": {
"name": "Y"
}
}
- $..name → [“X”, “Y”]
⚠️ 注意:$..* 会返回所有值(包括嵌套结构),慎用。
3.4 通配符(*)
- 匹配当前层级的所有字段(对象)或所有元素(数组)。
示例:
ounter(line
{ "a": 1, "b": 2 }
- $.* → [1, 2]
数组示例:
ounter(line
[{"x":1}, {"x":2}]
- $[*].x → [1, 2]
3.5 数组索引与切片
基本索引
- $[0]:第一个元素
- $[-1]:最后一个元素(仅部分实现支持,如 Python 的 jsonpath-ng)
切片(Slice)
语法:[start:end:step],语义同 Python。
示例(假设数组长度为 5):
- $[1:3] → 第 1、2 个元素(不包含索引 3)
- $[:2] → 前两个元素
- $[::2] → 所有偶数索引元素
- $[::-1] → 反转数组(若支持)
❗ 多数 JavaScript 实现(如 jsonpath-plus)不支持切片。
3.6 过滤表达式(Filter Expression)
语法:[?(<boolean expr>)]
支持的比较操作符(因实现而异):
- ==, !=, <, <=, >, >=
- =~:正则匹配(部分实现支持)
- in:成员检查(如 @.category in ['fiction', 'sci-fi'])
逻辑操作符:
- &&(或 and)
- ||(或 or)
- !(或 not)
示例
ounter(lineounter(lineounter(lineounter(line
[
{ "name": "Alice", "age": 30 },
{ "name": "Bob", "age": 25 }
]
- $[?(@.age > 25)] → [{“name”:”Alice”,”age”:30}]
- $[?(@.name == 'Bob')] → [{“name”:”Bob”,”age”:25}]
正则匹配(Jayway 实现):
- $[?(@.name =~ /Ali.*/)]
⚠️ 注意:不同库对表达式语法的支持差异很大。例如:
Java (Jayway):支持 =~、empty、size() 等
Python (jsonpath-ng):支持基本比较和逻辑
JS (jsonpath-plus):支持有限比较,不支持正则
3.7 联合选择(Union)
语法:['key1','key2'] 或 [0,2,4]
示例:
ounter(line
{ "a": 1, "b": 2, "c": 3 }
- $['a','c'] → [1, 3]
数组示例:
ounter(line
["x", "y", "z"]
- $[0,2] → [“x”, “z”]
✅ 支持情况:Jayway、jsonpath-ng 支持;部分 JS 库不支持。
四、高级功能(依赖具体实现)
4.1 脚本表达式(Script Expression)
某些实现(如 Jayway)允许在路径中嵌入脚本:
- $[(@.length – 1)] → 获取最后一个元素(通过计算)
4.2 函数支持(Jayway 特有)
- length():获取数组或字符串长度
- $.store.book.length() → 2
- empty():判断是否为空
- $[?(@.tags empty false)]
- size():类似 length
4.3 投影(Projection)
非标准但实用的扩展(如 jsonpath-ng.ext):
- $.store.book[*].{title: @.title, price: @.price}
❗ 标准 JSONPath 不支持对象构造,这是扩展功能。
五、主流实现对比
|
功能 |
Jayway (Java) |
jsonpath-ng (Python) |
jsonpath-plus (JS) |
gjson (Go) |
|
$.. 递归 |
✅ |
✅ |
✅ |
✅(但语法不同) |
|
?() 过滤 |
✅(强劲) |
✅(基本) |
✅(有限) |
❌ |
|
=~ 正则 |
✅ |
❌ |
❌ |
❌ |
|
切片 [1:3] |
❌ |
✅ |
❌ |
❌ |
|
负索引 [-1] |
❌ |
✅ |
❌ |
✅(通过 #) |
|
联合选择 [0,2] |
✅ |
✅ |
❌ |
❌ |
|
函数 length() |
✅ |
❌ |
❌ |
内置函数 |
|
通配符 * |
✅ |
✅ |
✅ |
✅ |
提议:在跨语言项目中,尽量使用 最小子集($, ., [], .., *, ?(@.x > y))以保证兼容性。
六、常见使用场景与表达式模板
|
场景 |
JSONPath 表达式 |
|
获取所有 ID |
$..id |
|
获取第一个用户 |
$.users[0] |
|
获取价格在 10~20 之间的商品 |
$[?(@.price >= 10 && @.price <= 20)] |
|
获取嵌套对象中的 name |
$..profile.name |
|
获取所有非空标签 |
$[?(@.tags && @.tags.length > 0)] |
|
获取特定键的值(键含空格) |
$['my key']['sub key'] |
|
获取数组最后元素(Python) |
$[-1] |
|
获取所有书的作者和标题 |
$..book[*]['author','title'] (若支持联合) |
七、常见陷阱与注意事项
- 路径不存在 ≠ 错误
- 多数实现返回空数组 [],而非抛异常。
- 需要显式检查结果是否为空。
- 递归下降性能问题
- $..* 在大型 JSON 中可能导致 O(n²) 遍历。
- 字符串比较 vs 数值比较
- @.price == '10' 与 @.price == 10 结果不同(类型敏感)。
- 键名大小写敏感
- $.Name ≠ $.name
- 数组 vs 对象混淆
- 对象不能用数字索引:$.obj[0] 无效(除非 obj 是数组)
- 转义字符
- 若键名含单引号,需转义:$['O'Reilly']
八、调试与测试工具
- 在线解析器:
- https://jsonpath.com/ (基于 jsonpath-plus)
- https://jsonpath.herokuapp.com/ (Jayway 实现)
- 命令行工具:
- jp(Go 编写):echo '{“a”:1}' | jp a
- jq 虽非 JSONPath,但功能更强(推荐搭配使用)
九、JSONPath vs jq
|
特性 |
JSONPath |
jq |
|
标准化 |
无正式标准 |
广泛使用,实际标准 |
|
功能 |
查询为主 |
查询 + 转换 + 构造 |
|
语法 |
类 XPath |
类函数式编程 |
|
学习曲线 |
低 |
中高 |
|
适用场景 |
简单提取 |
复杂数据处理 |
提议:简单提取用 JSONPath,复杂处理用 jq。
十、总结
JSONPath 是一个轻量级、直观的 JSON 查询语言,适用于:
- API 响应字段提取
- 配置文件解析
- 自动化测试中的断言
- 日志分析中的字段抽取