JSONPath Tutorial: Query JSON Like a Pro
Master JSONPath syntax with practical examples. Learn every operator, filter expression, and real-world query pattern to extract data from complex JSON structures.
什么是 JSONPath?
JSONPath 是一种用于 JSON 的查询语言,类似于 XPath 对 XML 的作用。它允许您深入复杂的 JSON 结构,使用简洁的表达式语法提取所需的数据。您无需编写循环和条件语句来遍历嵌套的对象和数组,而是可以编写一个单一的路径表达式,例如 $.store.book[?(@.price < 10)].title,并获取所有价格低于十美元的书名。
Stefan Goessner 于 2007 年引入了 JSONPath,随后它成为了跨语言和工具的事实标准。如果您处理 REST API、配置文件或任何非平凡的 JSON 数据,JSONPath 将为您节省大量时间。
JSONPath 与 XPath:快速比较
如果您已经了解 XPath,JSONPath 将会让您感到熟悉。关键区别在于 JSON 具有更简单的数据模型——对象和数组,而不是元素、属性和命名空间。以下是两者的比较:
| 概念 | XPath | JSONPath |
|---|---|---|
| 根 | / | $ |
| 子 | /child | .child 或 ['child'] |
| 递归下降 | // | .. |
| 通配符 | * | * |
| 数组索引 | [1] (1 基于) | [0] (0 基于) |
| 过滤 | [predicate] | [?(expression)] |
完整的 JSONPath 操作符参考
每个 JSONPath 表达式都从根 $ 开始,并链接操作符以深入导航。以下是您需要了解的所有操作符:
| 操作符 | 描述 | 示例 |
|---|---|---|
$ | 根对象或数组 | $ — 整个文档 |
.key | 按名称访问子成员(点表示法) | $.store.name |
['key'] | 按名称访问子成员(括号表示法) | $['store']['name'] |
.. | 递归下降 — 搜索所有后代 | $..price — 每个 "price" 字段 |
* | 通配符 — 对象或数组的所有成员 | $.store.* |
[n] | 数组索引(0 基于) | $.items[0] |
[start:end] | 数组切片(结束是排除的) | $.items[0:3] — 前三个项目 |
[start:end:step] | 带步长的数组切片 | $.items[::2] — 每隔一个项目 |
[n,m] | 多个数组索引 | $.items[0,2,4] |
[?()] | 过滤表达式 | $.items[?(@.price > 10)] |
@ | 当前节点(在过滤器内部使用) | @.name == 'Alice' |
示例 JSON 用于我们的示例
我们将在整个教程中使用此 JSON 文档。它代表一个小型书店 API 的响应:
{
"store": {
"name": "TechBooks Online",
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95,
"inStock": true
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99,
"inStock": false
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99,
"inStock": true
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99,
"inStock": true
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
实用的 JSONPath 示例
1. 获取商店名称
$.store.name
结果:
"TechBooks Online"
2. 获取所有书名
$.store.book[*].title
结果:
[
"Sayings of the Century",
"Sword of Honour",
"Moby Dick",
"The Lord of the Rings"
]
3. 获取第一本书
$.store.book[0]
结果:整个第一本书对象。
4. 获取最后一本书
$.store.book[-1]
负索引从末尾计数。-1 给您最后一个元素。
5. 切片:前两本书
$.store.book[0:2]
返回索引 0 和 1 的书。结束索引是排除的,就像在 Python 和 JavaScript 的 slice() 中一样。
6. 获取整个文档中的所有价格
$..price
结果:
[8.95, 12.99, 8.99, 22.99, 19.95]
递归下降操作符 .. 找到每个 price 字段,无论深度如何——包括自行车的价格。
7. 过滤:价格低于 $10 的书
$.store.book[?(@.price < 10)]
返回价格为 8.95 和 8.99 的两本书。@ 符号指当前正在评估的项。
8. 过滤:有 ISBN 的书
$.store.book[?(@.isbn)]
仅返回《白鲸》和《指环王》。
9. 过滤:有库存的小说书籍
$.store.book[?(@.category == 'fiction' && @.inStock == true)]
您可以使用 &&(与)和 ||(或)组合条件。
10. 多个索引
$.store.book[0,3]
选择特定元素。返回第一本和第四本书。
11. 对象上的通配符
$.store.bicycle.*
结果:
["red", 19.95]
返回自行车对象的所有值。
12. 获取昂贵书籍的作者
$.store.book[?(@.price > 15)].author
结果:
["J. R. R. Tolkien"]
自己试试: 将上面的示例 JSON 粘贴到我们的 JSONPath 测试器 中进行实验。
过滤表达式深入解析
过滤表达式是 JSONPath 中最强大的部分。它们位于 [?()] 内部,并为每个元素评估布尔条件。以下是您可以使用的操作符:
| 操作符 | 意义 | 示例 |
|---|---|---|
== | 等于 | @.status == 'active' |
!= | 不等于 | @.role != 'admin' |
> | 大于 | @.age > 18 |
>= | 大于或等于 | @.score >= 90 |
< | 小于 | @.price < 50 |
<= | 小于或等于 | @.quantity <= 0 |
=~ | 正则匹配(某些实现) | @.name =~ /^J.*/ |
&& | 逻辑与 | @.price > 5 && @.price < 20 |
| ` | ` |
一个常见的模式是检查属性的存在性。写 @.isbn 而不带比较操作符,如果属性存在且不为 null,则返回 true。
现实世界场景
从 API 响应中提取所有电子邮件
假设您从用户管理 API 收到一个响应,其中包含深度嵌套的联系对象:
{
"users": [
{
"name": "Alice",
"contacts": { "email": "alice@example.com", "phone": "555-0101" }
},
{
"name": "Bob",
"contacts": { "email": "bob@example.com", "phone": "555-0102" }
}
]
}
查询:$..email
结果:
["alice@example.com", "bob@example.com"]
递归下降操作符找到每个 email 字段,无论它在层次结构中的位置。
按价格范围查找产品
$.products[?(@.price >= 25 && @.price <= 100)]
在渲染产品列表之前,客户端过滤 API 响应时非常有用。
获取嵌套配置值
$.config.database.connections[0].host
深入配置文件,而无需编写链式可选访问,如 config?.database?.connections?.[0]?.host。
不同语言中的 JSONPath
JavaScript / Node.js
jsonpath-plus 包是最流行的实现:
import { JSONPath } from "jsonpath-plus";
const data = { store: { book: [{ title: "Moby Dick", price: 8.99 }] } };
const titles = JSONPath({ path: "$.store.book[*].title", json: data });
console.log(titles); // ["Moby Dick"]
Python
使用 jsonpath-ng 进行符合标准的实现:
from jsonpath_ng.ext import parse
data = {"store": {"book": [{"title": "Moby Dick", "price": 8.99}]}}
expr = parse("$.store.book[*].title")
matches = [match.value for match in expr.find(data)]
print(matches) # ['Moby Dick']
Java
Jayway JSONPath 库是 JVM 项目的首选:
import com.jayway.jsonpath.JsonPath;
String json = "{\"store\":{\"book\":[{\"title\":\"Moby Dick\",\"price\":8.99}]}}";
List<String> titles = JsonPath.read(json, "$.store.book[*].title");
System.out.println(titles); // [Moby Dick]
注意事项和限制
JSONPath 功能强大,但在您在生产环境中依赖它之前,有一些事项需要注意:
- 没有标准规范(直到最近)。 JSONPath 最初是非正式定义的,不同的库以微妙的差异实现它。IETF 于 2024 年发布了 RFC 9535,以标准化语法,但许多库仍遵循原始的 Goessner 规范。始终检查您库的文档以了解支持的功能。
- 正则表达式支持各异。
=~正则表达式操作符并非普遍支持。Jayway Java 库支持它;许多 JavaScript 库则不支持。 - 没有写操作。 JSONPath 是只读的。您无法使用它修改原始文档。对于变更,您需要 JSON Patch(RFC 6902)或手动遍历。
- 在大型文档上的性能。 递归下降(
..)扫描树中的每个节点。在包含数百万个节点的文档上,这可能会很慢。当性能重要时,优先选择特定路径。 - 返回类型不一致。 一些库对单个匹配返回单个值,对多个匹配返回数组。其他库始终返回数组。请在代码中规范返回类型,以避免意外。
- 键中的特殊字符。 如果您的 JSON 键包含点、空格或其他特殊字符,请使用括号表示法:
$('my.key')而不是$.my.key。
JSONPath 与 jq
您可能想知道 JSONPath 与流行的命令行 JSON 处理器 jq 的比较。简而言之:它们解决类似的问题,但面向不同的受众。
- JSONPath 旨在嵌入应用程序代码中。它在每种主要语言中都有库支持,并且查询语法简单明了。
- jq 是一个完整的数据转换语言。它可以过滤、映射、归约和重塑 JSON。它更强大,但学习曲线更陡峭,主要用于命令行。
对于在应用程序内部查询数据,JSONPath 是务实的选择。对于 shell 脚本和数据管道,jq 难以超越。
快速参考备忘单
| 您想要的内容 | JSONPath |
|---|---|
| 整个文档 | $ |
| 特定属性 | $.user.name |
| 数组中的所有项目 | $.items[*] |
| 第一个项目 | $.items[0] |
| 最后一个项目 | $.items[-1] |
| 项目的范围 | $.items[1:4] |
| 每隔 n 个项目 | $.items[::2] |
| 所有 "name" 字段 | $..name |
| 按值过滤 | $.items[?(@.price < 10)] |
| 按存在性过滤 | $.items[?(@.discount)] |
| 多个条件 | $.items[?(@.qty > 0 && @.active)] |
自己试试: 打开 JSONPath 测试器,实时运行查询以处理您自己的 JSON 数据。