
浏览器中的JSONPath:实际使用模式的实用参考和测试器
📷 Pixabay / Pexels浏览器中的JSONPath:实际使用模式的实用参考和测试器
停止猜测你的JSONPath查询是否会匹配你期望的内容。一个关于过滤器、切片和递归下降运算符的实用指南 — 附带免费测试器。
我使用API的时间长到足以记住XML是默认值、XPath是每个人都学习的查询语言的时代。JSONPath是JSON的等价物,在多年避开它之后(因为我可以直接写JavaScript,对吧?),我回心转意了。JSONPath是唯一可用语言的地方太多了 — Postman变量、AWS Step Functions、Kubernetes选择器、Datadog过滤器 — 流利使用它能节省真实的时间。
本指南是我刚开始时希望拥有的参考。我们将涵盖实际在生产中出现的语法、让我花费数小时的陷阱,以及ToolBox Hubs的JSONPath测试器如何让你在将表达式粘贴到任何重要位置之前检查它们。
简要介绍JSONPath
JSONPath是JSON文档的查询语言。你给它一个表达式如$.store.book[*].author,它返回所有匹配的值。$是根,点和方括号在结构中导航,特殊运算符让你过滤、切片和递归遍历。
如果你使用过XPath处理XML,心理模型可以直接转移。如果没有,把它想象成JavaScript中点表示法的更强大版本 — 一种可以用一行表达「价格低于10美元的所有书」或「文档中任何深度的每个作者」的版本。
每个人都使用的经典示例来自Stefan Goessner的原始文章:
{
"store": {
"book": [
{ "category": "fiction", "author": "Tolkien", "price": 22.99 },
{ "category": "reference", "author": "Rees", "price": 8.95 }
],
"bicycle": { "color": "red", "price": 19.95 }
}
}
$.store.book[*].author返回["Tolkien", "Rees"]。$..price返回[22.99, 8.95, 19.95]。$.store.book[?(@.price < 10)]返回第二个book对象。这就是全部游戏。
你90%时间会用到的运算符
JSONPath有几十个运算符。其中五个涵盖了你在实践中编写的几乎所有内容。
$ — 根
每个JSONPath表达式都以$开头。它指文档的顶部。从技术上讲,你可以省略它(一些库很宽松),但显式写出会使意图明确。
.field — 子访问
标准点表示法。$.users.0.email从根走到users数组,选择索引0,并读取email。与JavaScript相同,只是0作为属性名工作。
[n] — 数组索引
数组访问的方括号表示法,在索引是动态的或字段名有特殊字符时有用。$.users[0]、$.users[-1](最后一项)、$.users[0:3](从0到3的切片,不包含3)。
负索引从末尾计数。$.users[-1]是最后一个用户。$.users[-3:]给出最后三个。这并非在每个实现中都支持 — RFC 9535标准化了它,但如果你依赖它,请检查你的库。
[*] — 通配符
匹配数组中的所有项或对象中的所有值。$.users[*].email返回每个用户的电子邮件。$.config.*返回config对象中的每个值。与属性访问结合,这就是你扁平化常见结构的方式。
..field — 递归下降
最强大的运算符,也是我最常使用的运算符。$..price找到文档中任何位置的每个price字段,无论嵌套多深。当你不知道或不关心确切结构时很有用。
下降不关心路径 — 它只是找到所有匹配模式的内容。这使它非常适合临时数据探索(「此响应是否包含任何用户ID?」),如果你的数据在不同上下文中具有相同的键名,则很危险(用户的name字段和角色的name字段都将匹配$..name)。
过滤器:人们卡住的地方
过滤表达式写为[?(...)],其中内容是针对数组中每个项评估的布尔表达式。在过滤器内,@指当前项。
$.store.book[?(@.price < 10)]
$.users[?(@.role == 'admin')]
$.events[?(@.timestamp > 1700000000)]
@是让人绊倒的部分。它相当于forEach中的this或当前项。没有它,过滤器无法引用任何字段 — [?(price < 10)]不起作用,因为price未定义;引擎不知道要查看当前项内部。
比较运算符
跨库通用:==、!=、<、>、<=、>=。
右侧可以是字面量:数字(@.age > 18)、单引号字符串(@.role == 'admin')、双引号字符串、true、false或null。
NOT通用的:相互比较两个字段(@.price > @.cost)、正则表达式匹配(@.email =~ /.*@example\.com/)、像length()或match()这样的函数调用。一些库支持这些;许多不支持。ToolBox Hubs的JSONPath测试器支持针对字面值的基本比较,这涵盖了绝大多数实际查询。
布尔组合
标准实现支持&&和||组合条件:
$.products[?(@.in_stock == true && @.price < 50)]
$.events[?(@.severity == 'error' || @.severity == 'critical')]
如果你发现自己在JSONPath中编写复杂的布尔逻辑,这通常是一个信号,要降到实际代码 — 你六个月后阅读表达式的未来自我会感谢你。JSONPath擅长简单选择;复杂的业务逻辑属于真正的语言。
切片:当你只需要一些项时
[start:end:step]的工作方式与Python切片完全相同。
[0:5]— 前5项[5:]— 从索引5开始[:3]— 前3项(与[0:3]相同)[-3:]— 最后3项[::2]— 每隔一项[::-1]— 反转(并非处处支持)
切片是JSONPath中较干净的部分之一。它是Python的直接移植,行为可预测。我经常用它来处理「给我前N个结果」或「给我除第一项外的所有内容」模式。
工作中实际出现的模式
理论很好;让我们看看实际的查询。
过滤API响应
你的API返回分页结果,你想找到特定项。在你的测试框架或Postman中的JSONPath:
$.data[?(@.status == 'active')].id
返回所有活动项的ID。如果API没有「按状态过滤」参数,这比在代码中转换更快。
从webhook负载中提取值
你正在处理来自Stripe、GitHub或类似平台的webhook,你需要获取特定字段。AWS EventBridge规则、Step Functions或Logic Apps中的JSONPath:
$.detail.payload.items[*].sku
返回订单中每项的SKU。云工作流工具依赖JSONPath,因为它是声明性和可检查的,而嵌入JavaScript将是安全和可维护性的负担。
在深度嵌套数据中查找项
你不知道确切结构,但你知道某处有用户ID:
$..userId
返回文档中的每个userId字段,无论嵌套深度。在使用不熟悉的API响应或第三方集成时进行探索很有用。
验证类似模式的约束
你需要检查列表中的所有项是否都有特定字段:
$.users[?(!@.email)]
返回缺少电子邮件的用户。如果结果为空,你的数据是干净的。这种模式在CI测试脚本中工作良好,你解析JSON输出并对其结构进行断言。
陷阱(个人列表)
这些是让我花费数小时的事情。
过滤器中的@是必需的。 写[?(price < 10)]而不是[?(@.price < 10)]是最常见的错误。没有@,引擎不知道price指当前项上的字段。
过滤器返回匹配项,而不是你过滤的字段。 $..book[?(@.price < 10)]返回book 对象,不是价格。要只获取价格:$..book[?(@.price < 10)].price。
表达式内的引号很重要。 在JSONPath字符串内使用单引号,以避免与周围的语言混淆。在JSON配置文件中,你的JSONPath是字符串值,所以JSONPath本身使用单引号:"$.users[?(@.role == 'admin')]"。混淆它们会产生解析错误。
递归下降找到影子匹配。 $..name将匹配每个name字段,包括不相关嵌套对象中的字段。如果你在同一文档中有用户name字段和产品name字段,这将返回两者。当重要时要具体。
索引0对属性'0'。 $.items[0]在数组上工作。$.items.0可能或可能不工作 — 大多数解析器拒绝它用于数组(它仅限方括号),但接受它作为对象的属性名。坚持使用方括号进行数组访问。
空结果不是错误。 如果你的JSONPath不匹配任何内容,你会得到空数组[],而不是异常。这是正确的行为,但它可以隐藏错字。如果你期望结果但没有得到,请仔细检查路径。
RFC 9535与Goessner时代的库
定义JSONPath的2007年原始文章是非正式的 — 它用示例描述语法,但没有正式指定边缘情况下的行为。不同的库实现做出了不同的选择,多年来差异累积。
2024年,RFC 9535作为官方规范发布。它标准化语法和语义,特别是围绕:
- 过滤表达式语法
- 函数扩展(
length、count、match、search、value) - 输出中的规范化路径(每个结果包括其位置)
- 用于正则表达式匹配的I-Regexp配置文件
如果你在开始一个新项目,优先选择针对RFC 9535的库 — Node的jsonpath-rfc9535、Python端口jsonpath-rfc9535、jp CLI工具。如果你在维护现有代码,你可能在Goessner时代的实现上;那没关系,只是要知道差异存在。
ToolBox Hubs的JSONPath测试器实现了在实现之间一致工作的最常见运算符。它在过滤器语法上有意保守,以避免测试在你的目标环境中无法工作的内容。
JSONPath输给jq的地方
如果我不提jq,我就在误导你。
JSONPath擅长在JSON中查找值。jq擅长查找AND转换JSON。如果你需要:
- 将对象投影到不同形状(
.users | map({id, email})) - 聚合值(
map(.price) | add) - 组合多个操作
- 处理复杂的条件逻辑
jq要好得多。JSONPath没有聚合,没有map/reduce,没有函数组合。这些不是bug — 是范围决策。JSONPath是查询语言。jq是转换语言。
正确的工具取决于你的环境。JSONPath无处不在,因为它易于嵌入(没有依赖,只是一个字符串解析器)。jq需要一个二进制文件,在浏览器上下文或受限环境中更难使用。在Postman测试或Step Function输入映射中,你只有JSONPath。在shell管道或服务器端数据转换中,jq通常更好。
实用工作流
这是我现在实际使用JSONPath的方式:
-
在JSON格式化器中打开API响应或JSON文档以使其可读。
-
识别我想要提取的值。通常我可以心里追踪路径:「users数组,项N,role字段,等于admin。」
-
打开JSONPath测试器,粘贴JSON,并写表达式。迭代直到它准确返回我想要的内容。
-
将工作表达式复制到它需要存在的地方 — Postman测试、AWS Step Function、Kubernetes选择器。
-
如果我在构建更复杂的东西(转换、聚合),我切换到jq。
这个工作流从集成中削减了「为什么我的JSONPath不匹配」调试的数小时。测试器是大多数教程跳过的缺失步骤 — 它们展示语法并假设你可以第一次尝试就正确编写。你通常不能,特别是过滤器。
更广泛JSON工作流的工具推荐
JSONPath是JSON工作流中的一个工具。将其与相关实用程序配对使整个管道更顺畅:
- JSON格式化器 — 在查询前漂亮打印;压缩的JSON不可读
- JSON Diff — 当JSONPath在两个响应之间返回不同结果时,对它们进行diff以查看更改了什么
- JSON到TypeScript — 从样本生成TypeScript类型,然后使用类型编写匹配模式的JSONPath
- JSON模式生成器 — 生成用于验证的JSON模式;与用于查询的JSONPath互补
JSONPath测试器是免费的,仅浏览器,从不将你的JSON发送到任何地方。粘贴、查询、迭代、复制。这就是循环。
结束语
JSONPath并不光鲜。它不是任何人都为学习而兴奋的语言。但花一个小时变得流利会在API调试、云工作流配置和数据探索的多年中得到回报。语法足够小,可以记住常见模式。陷阱足够可预测,可以内化。
如果你是新手:从基本模式开始($.field、$..field、$.array[*]、[?(@.field == 'value')]),并避免过滤器复杂性,直到你使用简单的东西一段时间。大多数现实世界的查询都很简单。复杂性来自通常用代码更好地解决的边缘情况。