JSONPath 是从 JSON 值中选择节点的查询语言,设计受到 XPath 启发,但语法和结果模型属于 JSON 生态。历史上“JSONPath”这个名称覆盖了多个不兼容实现;RFC 9535 现在定义了标准语法和语义,但各库仍可能提供正则、聚合函数、脚本、回调或修改数据等扩展。
核心要点
- 分享查询前先确认方言。一个库可执行的路径,可能是另一个库不支持的扩展,或结果顺序不同。
- JSONPath 主要是读查询语言。更新数据、调用回调和执行脚本属于实现特性,有独立安全风险。
$、子节点、通配符、递归、索引、切片和过滤器是常用基础;宽泛递归可能很昂贵。- JSONPath 返回选中值/节点,JSON Pointer 标识一个位置,JMESPath 更强调投影和变换,三者不能互换。
- 用户提供的表达式和过滤值都应视为不可信输入。使用允许列表,关闭脚本/函数扩展,限制深度、结果、时间和内存。
- 查询成功不代表对象已授权,也不代表返回值可以安全披露。评估前后都要执行租户、字段和用途授权。
JSONPath 与标准化
2007 年的原始 JSONPath 提案影响了大量库,但没有形成一个所有实现都一致的语法。RFC 9535 定义了根 $、子节点和递归段、通配符、数组选择器及过滤表达式等标准查询模型。
标准化并不会让所有历史功能都可移植。API 契约中应记录:
- 方言和库版本;
- 结果是否保留节点顺序和重复项;
- 缺失值和标量根的处理;
- 启用哪些过滤器运算符和函数扩展;
- 查询与结果的最大限制。
核心选择器
考虑以下 JSON:
{
"store": {
"books": [
{ "title": "Systems", "price": 79.99, "inStock": true },
{ "title": "Algorithms", "price": 89.99, "inStock": false }
],
"location": { "city": "San Francisco" }
}
}
| 查询 | 意图 |
|---|---|
$ |
选择根值 |
$.store |
选择子成员 |
$['store']['location']['city'] |
使用括号语法选择成员 |
$.store.books[0] |
选择数组索引 |
$.store.books[*].title |
选择所有数组元素的标题 |
$..price |
递归选择匹配的成员名 |
$.store.books[0:2] |
在方言支持时选择切片 |
当键名包含空格、标点或点语法无法表达的字符时,括号语法更合适。切片语法和索引规则仍需核对部署的实现和标准配置。
过滤器与表达式
过滤器对候选节点计算谓词。可移植过滤器应使用简单比较和明确的存在性语义:
$.store.books[?(@.price > 80 && @.inStock == false)].title
不要假定所有引擎都支持:
=~等正则运算符;- 任意脚本表达式;
- 用户自定义函数;
avg()、stddev()等聚合函数;apply()、update()等修改方法。
这些能力会把有界选择器变成表达式运行时。如果产品需要,必须定义安全子集,并作为独立契约测试。
结果语义
一次查询可以返回零个、一个或多个匹配项。实现可能返回值、路径、节点,或三者的包装。需要明确:
- 路径缺失是空结果还是错误;
- 重复匹配是否保留;
- 顺序是否遵循文档遍历;
- 是否允许查询标量根;
null如何区别于没有匹配;- 结果是否引用可变内存对象。
在 API 断言中,应明确结果数量。“查询返回空列表”不一定等于“必填字段缺失”。
JSONPath、JSON Pointer 与 JMESPath
| 工具/语言 | 主要角色 | 常见结果 |
|---|---|---|
| JSONPath | 从 JSON 树选择一个或多个节点 | 取决于实现的值/节点/路径 |
| JSON Pointer(RFC 6901) | 用转义 token 标识一个位置 | /store/books/0/title 这样的路径 |
| JMESPath | 查询加投影和变换 | 计算后的 JSON 值 |
| JSON Schema | 断言结构和约束 | 有效/无效及错误 |
需要补丁或授权策略的明确位置时使用 JSON Pointer;需要校验结构时使用 JSON Schema。不要用 JSONPath 代替对象所有权或字段级授权。
使用固定库版本的 JavaScript 示例
库 API 和过滤器语法必须固定并测试。下面是只读查询示例,不接受不可信调用者传入的查询:
import { JSONPath } from "jsonpath-plus";
const data = {
store: {
books: [
{ title: "Systems", price: 79.99, inStock: true },
{ title: "Algorithms", price: 89.99, inStock: false },
],
},
};
const titles = JSONPath({
path: "$.store.books[*].title",
json: data,
});
const unavailable = JSONPath({
path: "$.store.books[?(@.inStock == false)].title",
json: data,
});
console.log({ titles, unavailable });
不可信路径不要启用脚本或回调能力,除非库提供了严格隔离的执行器。回调可能把只读查询变成副作用边界。
Python 与 Java 要点
Python 的 jsonpath-ng 提供核心解析器和语法不同的扩展解析器。应明确导入、包版本和扩展:
from jsonpath_ng import parse
data = {
"store": {
"books": [
{"title": "Systems", "price": 79.99},
{"title": "Algorithms", "price": 89.99},
]
}
}
expression = parse("$.store.books[*].title")
titles = [match.value for match in expression.find(data)]
print(titles)
Java 中的 Jayway JsonPath 与其他库在 provider、默认配置、过滤行为和返回类型上可能不同。应固定依赖,配置 JSON provider,并用 fixture 测试路径,不要直接复制其他实现的语法。
查询安全与资源限制
如果用户可以提供 JSONPath 表达式,应把它当作小型程序:
- 只允许批准的路径模板或受限语法;
- 拒绝脚本、函数、回调和修改扩展;
- 限制表达式长度、递归深度、递归遍历、候选节点、结果数量和执行时间;
- 禁止查询内部抓取任意远程文档;
- 在评估前授权租户、对象、字段和用途;
- 对敏感值脱敏并限制序列化结果大小。
对大型文档执行宽泛的 $..* 可能耗尽资源。超时或资源耗尽时应 fail closed,不能返回部分结果让调用者误认为完整答案。
API 与配置查询
API 测试应使用稳定路径,并同时断言值和数量。$.users[*].email 即使返回空列表也可能通过,因此应与 JSON Schema 和业务断言组合。
配置中,JSONPath 可以定位字段,却不能证明修改权限。写入前应执行 Schema、环境策略、密钥处理和授权。相比库的 mutation 回调,更应使用带版本前置条件的声明式补丁格式。
分析场景应生成有界投影,而不是递归扫描每个请求。记录查询方言、库版本、输入修订、策略、结果数和失败状态。
常见错误
| 错误 | 为什么失败 | 更好的方法 |
|---|---|---|
| 把所有 JSONPath 语法当可移植 | 各库方言和扩展不同 | 固定方言并测试 fixture |
所有查询都用 $..* |
遍历宽泛且结果含义可能不清 | 选择有界子树 |
对不可信路径调用 update() |
查询变成修改和副作用边界 | 使用授权、版本化补丁 |
| 把空结果当作合法缺失 | 缺失和 null 语义不同 |
定义数量和 Schema 断言 |
| 接受用户原始路径 | 过滤器/脚本扩展可能被滥用 | 使用允许列表并关闭扩展 |
| 用 JSONPath 做授权 | 选择不等于所有权证明 | 单独检查租户/对象/字段策略 |
常见问题
JSONPath 是官方标准吗?
RFC 9535 定义了 JSONPath 标准,但旧库和 2007 年原始提案存在不同语法和扩展。共享查询契约必须写明方言与版本。
JSONPath 可以替代循环吗?
它可以简洁表达选择,但引擎内部仍然要遍历数据。复杂变换、连接、聚合、流式处理或明确错误处理可能更适合普通代码或其他查询语言。
JSONPath 可以修改 JSON 吗?
部分库提供更新 API,但修改不是 JSONPath 的可移植保证。应把它作为带授权、版本校验、幂等和输出校验的独立操作。
JSONPath 过滤器能安全接受用户输入吗?
默认不能。过滤器可能包含表达式求值或扩展。应使用允许列表/受限语法,关闭脚本和回调,并执行资源预算。
JSONPath 结果可以直接返回 API 吗?
只有完成授权、Schema 校验、脱敏、数量检查和输出限制后才可以。查询成功不能证明所有匹配字段都允许披露。
一手来源
- RFC 9535:JSONPath
- RFC 6901:JSON Pointer
- JSON Schema 2020-12
- JMESPath 规范
- jsonpath-plus 文档
- jsonpath-ng 文档
总结
当方言、结果语义和资源限制明确时,JSONPath 才适合稳定使用。把它用于有界只读查询,将校验和授权分开,并把执行代码或修改数据的扩展当作独立高风险功能。短表达式只有在运行边界上仍可移植、可测试且安全时才真正有价值。