JSONPath 是从 JSON 值中选择节点的查询语言,设计受到 XPath 启发,但语法和结果模型属于 JSON 生态。历史上“JSONPath”这个名称覆盖了多个不兼容实现;RFC 9535 现在定义了标准语法和语义,但各库仍可能提供正则、聚合函数、脚本、回调或修改数据等扩展。

核心要点

  • 分享查询前先确认方言。一个库可执行的路径,可能是另一个库不支持的扩展,或结果顺序不同。
  • JSONPath 主要是读查询语言。更新数据、调用回调和执行脚本属于实现特性,有独立安全风险。
  • $、子节点、通配符、递归、索引、切片和过滤器是常用基础;宽泛递归可能很昂贵。
  • JSONPath 返回选中值/节点,JSON Pointer 标识一个位置,JMESPath 更强调投影和变换,三者不能互换。
  • 用户提供的表达式和过滤值都应视为不可信输入。使用允许列表,关闭脚本/函数扩展,限制深度、结果、时间和内存。
  • 查询成功不代表对象已授权,也不代表返回值可以安全披露。评估前后都要执行租户、字段和用途授权。

JSONPath 与标准化

2007 年的原始 JSONPath 提案影响了大量库,但没有形成一个所有实现都一致的语法。RFC 9535 定义了根 $、子节点和递归段、通配符、数组选择器及过滤表达式等标准查询模型。

标准化并不会让所有历史功能都可移植。API 契约中应记录:

  • 方言和库版本;
  • 结果是否保留节点顺序和重复项;
  • 缺失值和标量根的处理;
  • 启用哪些过滤器运算符和函数扩展;
  • 查询与结果的最大限制。

核心选择器

考虑以下 JSON:

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] 在方言支持时选择切片

当键名包含空格、标点或点语法无法表达的字符时,括号语法更合适。切片语法和索引规则仍需核对部署的实现和标准配置。

过滤器与表达式

过滤器对候选节点计算谓词。可移植过滤器应使用简单比较和明确的存在性语义:

text
$.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 和过滤器语法必须固定并测试。下面是只读查询示例,不接受不可信调用者传入的查询:

javascript
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 提供核心解析器和语法不同的扩展解析器。应明确导入、包版本和扩展:

python
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 表达式,应把它当作小型程序:

  1. 只允许批准的路径模板或受限语法;
  2. 拒绝脚本、函数、回调和修改扩展;
  3. 限制表达式长度、递归深度、递归遍历、候选节点、结果数量和执行时间;
  4. 禁止查询内部抓取任意远程文档;
  5. 在评估前授权租户、对象、字段和用途;
  6. 对敏感值脱敏并限制序列化结果大小。

对大型文档执行宽泛的 $..* 可能耗尽资源。超时或资源耗尽时应 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 校验、脱敏、数量检查和输出限制后才可以。查询成功不能证明所有匹配字段都允许披露。

一手来源

总结

当方言、结果语义和资源限制明确时,JSONPath 才适合稳定使用。把它用于有界只读查询,将校验和授权分开,并把执行代码或修改数据的扩展当作独立高风险功能。短表达式只有在运行边界上仍可移植、可测试且安全时才真正有价值。