Skip to content
返回博客
教程

JSONPath 语法完全指南:查询与过滤 JSON(含示例)

用可复制示例学会 JSONPath 语法:根节点、递归下降、通配符、切片与过滤表达式,还有 RFC 9535 函数。每条查询都能在浏览器里实时测试。

11 分钟

JSONPath 语法完全指南:查询与过滤 JSON(含示例)

JSONPath 是 JSON 的查询语言,就像 XPath 之于 XML。你写一条路径表达式,求值器就会返回所有匹配的值。想从一份书店文档里取出全部作者名,只要写 $.store.book[*].author,作者列表就到手了,一行遍历代码都不用写。

本文会用可复制的示例把 JSONPath 语法里的每个选择器讲一遍,你可以边读边运行。有一点先说清楚:JSONPath 有两种方言。2007 年的 Goessner 方言是事实上的经典版,RFC 9535 则是 2024 年 2 月发布的 IETF 正式标准。它们在常见路径上一致,在边界情形上不同,本文会在差异出现的地方标出来。下面每条表达式你都能在 JSONPath 测试器里运行,并在两个引擎间切换对比。

先放一张选择器速查表,正文后面会用同一份 JSON 文档把每一行展开成可运行的示例。

选择器含义示例
$文档根节点$
@当前元素(filter 内)[?(@.price < 10)]
.name / ['name']子成员$.store.book
..递归下降$..author
*所有元素 / 成员$.store.book[*]
[0]数组索引$.store.book[0]
[start:end:step]数组切片(左闭右开)$.store.book[0:2]
[a,b]名称 / 索引的并集$.store.book[0,2]
[?()]过滤表达式$.store.book[?(@.price < 10)]
length() count() match() search() value()RFC 9535 函数(仅限 filter)[?length(@.title) > 15]

JSONPath 是什么?

JSONPath 是一种声明式查询语言,用来从 JSON 文档里选取节点。你不必写一个循环去遍历对象和数组,而是用一条路径描述想要的位置,求值器就返回匹配的值。它的思维模型和 XPath 之于 XML 完全一样:一条由选择器组成的路径,逐级穿过结构。

凡是开发者接触 JSON 的地方,它都会出现。你用它从 API 响应里取一个字段,在集成测试里对某个值做断言,在 Kubernetes、AWS Step Functions、Azure Logic Apps 的流水线配置里寻址字段,以及从庞大或不规则的 JSON 里提取数据而不用手写遍历逻辑。

简单交代一段历史,因为它解释了方言为何分裂。Stefan Goessner 在 2007 年提出了 JSONPath。它传播很快,成了事实标准,但从未被正式规范化,于是各实现在细节上渐渐走偏。IETF 在 2024 年 2 月用 RFC 9535 补上了这个缺口,这是首个正式的 JSONPath 规范。如今两种方言都还在用,这就是同一条表达式在不同库里行为可能不同的原因。

开始查询前,先把结构看清楚会更顺手。用 JSON 格式化工具把杂乱的输入美化一下,嵌套层次就一目了然了。

示例文档

下面每个示例都基于经典的 Goessner 书店 JSON。粘贴一次,反复复用:

{
  "store": {
    "book": [
      { "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
      { "title": "Sword of Honour", "author": "Evelyn Waugh", "price": 12.99 },
      { "title": "Moby Dick", "author": "Herman Melville", "price": 8.99 },
      { "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
    ],
    "bicycle": { "color": "red", "price": 19.95 }
  }
}

四本书,各有标题、作者、价格,外加一辆自行车。记住这一点:价格分别是 8.95、12.99、8.99 和 22.99,这会决定后面过滤的结果。

根、子节点与递归下降($ . ..)

每条表达式都从根开始,写作 $。从根出发,你用点号或方括号写法进入子节点,两者等价:

$.store.book          → the book array
$['store']['book']    → identical result

当键里含有空格、点号或其他特殊字符时,就得用方括号写法:$['first name'] 行得通,而 $.first name 不行。

.. 是递归下降(recursive descent)运算符。它搜索文档的每一层,而不只是直接子节点,并把任意深度上匹配后续选择器的内容全部收集起来:

$..author
→ ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]

何时用 ..,何时写全路径

递归下降很省事,但也很粗放。$..price 会匹配树里任何位置的 price,包括 store.bicycle.price,而这可能并不是你想要的。当你清楚数据形状时,就把路径写全,让查询保持精确:

$..price                  → [8.95, 12.99, 8.99, 22.99, 19.95]  (includes the bicycle)
$.store.book[*].price     → [8.95, 12.99, 8.99, 22.99]         (only books)

.. 留给真正不规则或未知的结构。这里要在便利和控制之间权衡:你对数据了解得越多,就越应该选择显式路径。

通配符、索引与数组切片(* [0] [start:end:step])

通配符 * 选取数组的所有元素,或对象的所有成员:

$.store.book[*].title
→ ["Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"]

数组索引从零开始,负索引从末尾倒数:

$.store.book[0].title     → ["Sayings of the Century"]
$.store.book[-1].title    → ["The Lord of the Rings"]

切片采用和 Python、JavaScript 相同的左闭右开(half-open)[start:end:step] 约定:start 包含在内,end 排除在外。

$.store.book[0:2].title   → ["Sayings of the Century", "Sword of Honour"]

它返回的是本书,不是三本:索引 0 和索引 1,到索引 2 之前停下。右端排除是 JSONPath 里最常见的一个坑,记牢它:

[0:2]   → first TWO elements (indices 0, 1)   ← correct
[0:3]   → first THREE elements (indices 0, 1, 2)

省略某个边界可以一直取到尽头,加一个步长则可每隔 N 个取一个:

$.store.book[2:].title    → ["Moby Dick", "The Lord of the Rings"]
$.store.book[:3].title    → first three titles
$.store.book[::2].title   → ["Sayings of the Century", "Moby Dick"]  (every other)

过滤表达式 [?()]——最强大的部分

过滤器是 JSONPath 真正发挥价值的地方。过滤器 [?()] 只保留谓词为真的元素,而在过滤器内部,@ 指代当前正在测试的元素。

要选出价格低于 10 的书:

$.store.book[?(@.price < 10)].title
→ ["Sayings of the Century", "Moby Dick"]

对照书店价格(8.95、12.99、8.99、22.99),有两本书符合。下面是一步步构建过滤谓词的方法:

  1. 与字面量比较。 使用 ==!=<<=>>=,例如 @.price > 10
  2. 匹配字符串。 字符串字面量用单引号:@.author == 'Nigel Rees'
  3. 测试是否存在。 一个裸成员引用会选出含有它的元素:[?(@.isbn)] 只保留有 isbn 的书。
  4. 组合条件。&&|| 连接多个谓词:[?(@.price < 10 && @.author == 'Herman Melville')]

最常见的过滤错误是 @ 的作用域。在谓词内部,当前元素是 @,不是 $。写成 $.price 会指回文档根,而不是正在测试的那本书:

$.store.book[?($.price < 10)]   → wrong scope, matches nothing useful
$.store.book[?(@.price < 10)]   → correct: each book's own price

filter 中的 RFC 9535 与 Classic 差异

两种方言在空白与引号上的处理不同。Classic 很宽松,[?(@.price<10)] 不带空格也能正确解析。RFC 9535 严格遵循自己的语法,对过滤器的写法要求更严。如果某个在别处能用的过滤器失败了,检查一下空格和引擎。把过滤器写干净(运算符两侧留空格、字符串用单引号),无论最终是哪个库来运行,结果都一致。

并集选择器——一次选取多个键([a,b])

并集选择器在一个方括号里列出多个名称或索引,把它们全部取出:

$.store.book[0]['title','author']
→ ["Sayings of the Century", "Nigel Rees"]

并集对索引同样适用,你也可以把它和其他选择器混用,做固定字段投影:

$.store.book[0,2].title          → ["Sayings of the Century", "Moby Dick"]
$.store.book[*]['title','price']  → title and price of every book

当你想要的是几个特定字段,而不是整个对象或一次通配扫描时,并集就是合适的工具。

RFC 9535 函数:length、count、match、search、value

RFC 9535 定义了五个标准函数扩展。有一条规则几乎人人都会栽,竞品指南也经常写错:

这些函数只能在过滤器 [?...] 内调用,绝不能作为独立的路径段。

$.store.book.length() 不是有效的 RFC 9535,标准语法会拒绝它。那种段调用形式是 jsonpath-plus 的扩展,并非规范的一部分。要按长度过滤,你得在谓词内部调用该函数:

$.store.book[?length(@.title) > 15]
→ [
    { "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
    { "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
  ]

被选中的两个标题都长于 15 个字符;“Moby Dick”(9)和 “Sword of Honour”(15,并非超过 15)都被排除在外。

下面是每个函数在过滤器内部的作用:

  • length():字符串、数组或对象的长度,如 [?length(@.title) > 15]
  • count():节点列表(nodelist)中的节点数,如 [?(count(@.authors) > 1)]
  • match():整串正则测试(I-Regexp 模式),如 [?match(@.author, 'J.*')]
  • search():子串正则测试,如 [?search(@.title, 'the')]
  • value():把单节点的节点列表转换为其值以便比较

这五个都是 RFC 9535 的特性。Classic(Goessner)方言并不实现它们,所以如果一个基于函数的表达式失败了,先确认你是在过滤器内部调用它,并且引擎已设为 RFC 9535。

RFC 9535 与 Classic Goessner——为何同一条表达式结果不同

当一条 JSONPath 表达式在两个工具里返回不同结果时,原因通常就是方言。下面是两者的对比:

方面Classic Goessner(2007)RFC 9535(2024)
标准化事实标准,从未正式化首个正式的 IETF 规范
filter 空白/引号宽松([?(@.price<10)] 可以)严格,完全遵循语法
缺失成员比较由实现自定义有明确定义,不抛错
标准函数不属于该方言length count match search value
规范化路径无规范形式规范化、单引号方括号形式
并集顺序因库而异已规定

实战建议:如果你的下游系统宣称符合 RFC 9535,就针对标准引擎编写和验证。如果你维护的是从 jsonpath.com、jsonpath-plus 或基于 Jayway 的服务复制来的表达式,就用 Classic,好让结果可复现。JSONPath 测试器用一个开关切换两个引擎,你可以粘贴一次表达式,并排看到每种方言如何处理它。要查清结果为何分歧,这种并排对比往往最直接。

JSONPath vs XPath vs jq——该用哪个

这三者经常被混为一谈,所以这里给个简短版:

  • JSONPath 是面向 JSON 的声明式路径查询。它最适合嵌入配置和测试断言,让你不必写代码就能指明一个位置。
  • XPath 是 XML 世界的对应物。JSONPath 借用了它的一些记号(*..[]),这正是类比成立的原因,但两种语言不可互换,函数集也不同。
  • jq 是命令行 JSON 处理器。它远不止路径选取,还能转换、聚合、重塑,而且就跑在你的 shell 管道里。

选哪个通常很清楚。要做嵌入式断言或流水线配置字段,选 JSONPath。要做 shell 驱动的转换和数据处理,选 jq,jq 速查表深入讲解了这套工作流。而当问题是某个 payload 是否符合预期形状、而非某个字段在哪里时,就用 JSON Schema 校验器及其完整校验指南来验证它。

7 个常见的 JSONPath 错误

  1. 忘了根 $ store.book 会被大多数引擎拒绝;每条表达式都以 $ 开头。
  2. 切片差一。 [0:2] 是两个元素,不是三个,右端边界排除在外。
  3. 方言搞错。 把 Classic 表达式跑在 RFC 9535 上(或反之)可能解析报错或匹配到不同节点。把引擎切到对应方言。
  4. 把函数当独立段。 $.store.book.length() 不是有效的 RFC 9535;要在过滤器内部调用 length()
  5. 过滤器里忘了 @ [?($.price < 10)] 指向根;要用 [?(@.price < 10)]
  6. 方括号引号写错。 $[store] 是错误;给键加引号:$['store']
  7. 以为 .. 只到第一层。 递归下降在每一层匹配,而非仅直接子节点。

在线测试 JSONPath,且保护隐私

学 JSONPath 语法最快的方式就是运行它。JSONPath 测试器能实时求值本文里的每一条表达式:RFC 9535 与 Classic 双引擎、Values / Paths / Both 三种结果视图、便于调试的规范化路径,以及 100% 浏览器本地执行。它不上传、不注册、不用 eval,所以用于专有 payload 也很安全。在这里搭一条路径,确认它精确选中你想要的节点,再把验证过的表达式直接粘进你的代码、测试或流水线。

至于 JSON 工作流的其余部分,可以用 JSON 转 TypeScript 把一份示例响应变成带类型的接口,或用 JSON Diff 逐字段对比两份文档。

常见问题

JSONPath 用来做什么?

JSONPath 不写命令式代码就能查询 JSON。开发者用它从 API 响应里取字段、在集成测试里对值做断言,以及在 Kubernetes、AWS Step Functions、Azure Logic Apps 的配置里寻址字段。它擅长从庞大或不规则的结构里提取数据,那种情况下手写遍历会很繁琐。

RFC 9535 和经典 JSONPath 有什么区别?

Classic 是 Stefan Goessner 2007 年的事实方言,被广泛实现却从未正式规范化,所以各库出现了分歧。RFC 9535 是 IETF 2024 年 2 月的正式规范:它定义了精确的语法、结果用的规范化路径,以及五个标准函数。两者在过滤器、并集和缺失成员比较等边界处不同。

JSONPath 过滤表达式是怎么工作的?

过滤器 [?()] 只保留谓词为真的元素,@ 是当前元素。例如 $.store.book[?(@.price < 10)] 选出价格低于 10 的书。你可以用 &&|| 组合条件、测试某个成员是否存在,以及与字符串或数字字面量比较。

我能把 length() 写成 $.store.book.length() 吗?

不能。在 RFC 9535 里,length() 和另外四个函数只能在过滤器内部调用,比如 $.store.book[?length(@.title) > 15]。独立段写法 $.store.book.length() 是 jsonpath-plus 扩展,不是标准 JSONPath,RFC 9535 语法会拒绝它。

JSONPath 和 XPath 是一回事吗?

不是,但思路相似。XPath 查询 XML,JSONPath 查询 JSON,两者都用路径选择器定位节点。JSONPath 刻意借用了 XPath 的部分记号(*..[]),这让类比很有用,但语法、语义和函数集都不同,且不可互换。

JSONPath 里的递归下降(..)做什么?

.. 运算符搜索文档的每一层,而不只是直接子节点。$..author 会把所有 author 成员收集起来,无论它出现在哪一层嵌套。它是从深层嵌套或不规则结构里提取单个字段的最快方式,但匹配到的节点可能远超预期,能收窄时就收窄。

标签: jsonpath json query-language rfc-9535 filter-expression developer-tools