JSONPath查询指南:如何搜索JSON数据
JSON已成为Web的通用语言。API返回JSON,配置文件使用JSON,数据库存储JSON文档。但随着JSON结构变得更大、嵌套更深,查找特定值变得越来越困难。这就是JSONPath的用武之地。JSONPath是一种查询语言,让你可以使用简洁的路径表达式来导航和提取JSON文档中的数据,就像XPath对XML所做的那样。本指南涵盖了从基本语法到高级过滤的所有内容,并提供了你可以立即应用的实用示例。
什么是JSONPath?
JSONPath是一种JSON查询语言,最初由Stefan Goessner于2007年提出。它提供了从JSON文档中选择节点的紧凑语法,类似于CSS选择器定位HTML元素或XPath表达式定位XML节点。你无需编写循环和条件逻辑来遍历JSON结构,只需编写一个描述通往所需数据路径的表达式。
JSONPath表达式从JSON文档的根节点开始,通过对象和数组导航到所需的值。该语言支持通配符、递归下降、数组切片和过滤表达式,足以满足大多数数据提取需求。
示例JSON文档
在本指南中,我们将使用以下JSON文档作为工作示例。这是原始JSONPath提案中的经典示例,并添加了额外数据:
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J.R.R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}JSONPath语法参考
JSONPath使用一小套运算符,它们组合形成强大的查询。以下是完整的语法参考:
| 运算符 | 描述 | 示例 |
|---|---|---|
| $ | 文档的根节点 | $ |
| . | 子运算符(访问对象属性) | $.store |
| [] | 数组索引或子运算符 | $.store.book[0] |
| [*] | 所有数组元素的通配符 | $.store.book[*] |
| .. | 递归下降(搜索所有层级) | $..author |
| .key | 命名的对象属性 | $.store.bicycle |
| ['key'] | 属性访问的括号表示法 | $['store']['book'] |
| [start:end] | 数组切片(结束索引不包含) | $.store.book[0:2] |
| [?()] | 过滤表达式 | < 10)] |
| () | 脚本表达式(取决于实现) | $.store.book[(@.length-1)] |
基本JSONPath表达式
访问根节点和直接子节点
美元符号$代表JSON文档的根节点。从那里,你使用点表示法访问对象属性,使用括号表示法访问数组索引。
| 表达式 | 结果 |
|---|---|
| $ | 整个JSON文档 |
| $.store | store对象(包含book数组和bicycle对象) |
| $.store.bicycle | {"color": "red", "price": 19.95} |
| $.store.bicycle.color | "red" |
| $.store.book | 整个book数组 |
| $.store.book[0] | 第一本书对象 |
| $.store.book[0].title | "Sayings of the Century" |
| $.store.book[3].author | "J.R.R. Tolkien" |
括号表示法
括号表示法是点表示法的替代方案,当属性名包含特殊字符、空格或以数字开头时很有用:
$.store['book'][0]['title']
$['store']['bicycle']['color']括号表示法和点表示法对于对象属性是可以互换的。然而,当属性名是动态的或包含在点表示法中无效的字符时,必须使用括号表示法。
通配符运算符
通配符运算符*匹配数组中的所有元素或对象中的所有属性。它对于在不知道特定键或索引的情况下提取某一层的所有值极其有用。
| 表达式 | 结果 |
|---|---|
| $.store.* | store对象中的所有值(book数组和bicycle对象) |
| $.store.book[*] | 数组中的所有书籍(全部四个book对象) |
| $.store.book[*].author | 所有作者姓名:["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J.R.R. Tolkien"] |
| $.store.*.price | bicycle的价格和book数组(非单本书的价格) |
递归下降:双点运算符
双点运算符..是JSONPath最强大的功能之一。它在JSON树的每一层搜索指定的键,而不仅仅是直接子节点。可以把它想象为对整个文档的深度搜索。
| 表达式 | 结果 |
|---|---|
| $..author | 文档中所有位置的author值 |
| $..price | 所有price值:[8.95, 12.99, 8.99, 22.99, 19.95] |
| $..book[2] | 第三本书,递归查找 |
| $..category | 所有category值:["reference", "fiction", "fiction", "fiction"] |
递归下降运算符在你不知道所需数据的精确路径,或相同的键出现在多个嵌套层级时特别有用。例如,如果API响应在各种嵌套层级包含id字段,$..id会收集所有id。
数组切片
数组切片让你从数组中选择一系列元素。语法[start:end]选择从起始索引到(但不包括)结束索引的元素。支持正索引和负索引。
| 表达式 | 结果 |
|---|---|
| $.store.book[0:2] | 前两本书(索引0和1) |
| $.store.book[1:3] | 第二和第三本书(索引1和2) |
| $.store.book[-1] | 最后一本书(The Lord of the Rings) |
| $.store.book[-2:] | 最后两本书 |
| $.store.book[:2] | 前两本书(同[0:2]) |
| $.store.book[2:] | 第三本书及之后 |
注意,切片行为在不同JSONPath实现之间可能略有差异。上述语法遵循大多数流行库和IETF RFC 9535标准使用的约定。
过滤表达式
过滤表达式是JSONPath真正变得强大的地方。它们让你根据条件而非位置选择元素。过滤语法使用[?(condition)],其中条件对每个元素进行评估。
比较运算符
JSONPath在过滤表达式中支持以下比较运算符:
| 运算符 | 含义 | 示例 |
|---|---|---|
| == | 等于 | [?(@.category == "fiction")] |
| != | 不等于 | [?(@.category != "fiction")] |
| < | 小于 | < 10)] |
| <= | 小于或等于 | <= 8.99)] |
| > | 大于 | [?(@.price > 15)] |
| >= | 大于或等于 | [?(@.price >= 12.99)] |
| =~ | 正则匹配(部分实现) | [?(@.author =~ /Tolkien/i)] |
实用过滤示例
使用我们的示例文档,以下是实用的过滤表达式及其结果:
Find all books cheaper than $10:
< 10)]返回书籍"Sayings of the Century"($8.95)和"Moby Dick"($8.99)。
Find all fiction books:
$.store.book[?(@.category == "fiction")]返回三本书:"Sword of Honour"、"Moby Dick"和"The Lord of the Rings"。
Find books with an ISBN:
$.store.book[?(@.isbn)]返回具有isbn属性的书籍:"Moby Dick"和"The Lord of the Rings"。这是因为过滤器检查属性是否存在。
Find the most expensive book:
$.store.book[?(@.price > 20)]返回"The Lord of the Rings"($22.99)。
过滤器中的逻辑运算符
你可以使用逻辑运算符组合条件:
< 10 && @.category == "fiction")]
$.store.book[?(@.price > 15 || @.category == "reference")]第一个表达式查找10美元以下的小说书籍(仅"Moby Dick")。第二个查找超过15美元或属于参考类别的书籍(返回"Sayings of the Century"和"The Lord of the Rings")。
JSONPath与XPath
JSONPath明确以XPath为模型,两种语言在概念上有许多相似之处。如果你熟悉XML处理,理解这种关系会有所帮助。
| 功能 | JSONPath | XPath |
|---|---|---|
| 根符号 | $ | / |
| 子级访问 | .key或['key'] | /element |
| 数组索引 | [0] | [1](从1开始) |
| 通配符 | * | * |
| 递归下降 | .. | // |
| 过滤 | [?(condition)] | [condition] |
| 属性 | 不适用(JSON没有属性) | @attr |
| 当前节点 | @(在过滤器中) | .或current() |
| 父级 | 不支持 | .. |
| 轴 | 不支持 | 13个轴(ancestor、following等) |
| 数据模型 | 对象和数组 | 元素、属性、文本节点 |
关键区别在于XPath操作在具有元素、属性、文本节点、命名空间和处理指令的丰富树模型上。JSONPath操作在更简单的对象(键值映射)和数组(有序列表)模型上。这种简单性使JSONPath更容易学习,但在复杂查询方面不如XPath表达力强。
JSONPath实践
API测试
JSONPath对于API测试不可或缺。当你向API发送请求并收到大型JSON响应时,JSONPath让你可以断言特定值,而无需手动导航整个结构。大多数API测试工具原生支持JSONPath。
例如,在测试中你可能验证第一本书的价格低于10美元:
// Using a JSONPath assertion in testing
response.jsonPath().get("store.book[0].price").should(equals(8.95));
// Find all books by a specific author
response.jsonPath().get("store.book[?(@.author == 'Herman Melville')].title");
// Returns: ["Moby Dick"]数据转换
当集成使用不同数据格式的系统时,JSONPath帮助提取和转换特定字段。你可以从一个JSON结构中提取值并映射到另一个,而无需编写复杂的遍历代码。
配置管理
复杂的配置文件通常包含深度嵌套的JSON。JSONPath让你可以查询特定配置值,而无需加载和解析整个结构。像jq这样的工具使用类似JSONPath的语法在命令行管道中处理JSON。
监控和告警
在可观测性系统中,JSONPath查询可以从JSON格式的日志和API响应中提取指标。你可以设置在JSONPath查询返回超过阈值的值时触发的告警。
JSONPath实现
JSONPath几乎在每种编程语言中都可用。以下是最流行的库:
| 语言 | 库 | 安装 |
|---|---|---|
| JavaScript | jsonpath-plus | npm install jsonpath-plus |
| Python | jsonpath-ng | pip install jsonpath-ng |
| Java | JsonPath (Jayway) | Maven: com.jayway.jsonpath |
| C# | Json.NET (Newtonsoft) | NuGet: Newtonsoft.Json |
| Go | gjson | go get github.com/tidwall/gjson |
| PHP | jsonpath | composer require softcreatr/jsonpath |
| Ruby | jsonpath | gem install jsonpath |
JavaScript示例
< 10)]',
json: data
});
// Returns books with price < 10
Python Example
from jsonpath_ng import parse
data = { /* our sample JSON */ }
# Find all authors
author_expr = parse('$..author')
authors = [match.value for match in author_expr.find(data)]
# ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J.R.R. Tolkien"]
# Find cheap books
cheap_expr = parse('$.store.book[?(@.price < 10)]')
cheap_books = [match.value for match in cheap_expr.find(data)]
Common Pitfalls and Tips
- Zero-based vs one-based indexing: JSONPath uses zero-based indexing (the first element is [0]), while XPath uses one-based indexing (the first element is [1]). This is a common source of off-by-one errors.
- Implementation differences: Before the IETF standard (RFC 9535), JSONPath implementations varied in their handling of edge cases like empty results, null values, and filter syntax. Always test your expressions with the specific library you are using.
- Filter performance: Recursive descent with filters (
$..book[?(@.price < 10)]) can be slow on large documents because it must traverse the entire tree. For performance-critical applications, use more specific paths when possible. - Case sensitivity: JSONPath is case-sensitive.
$.Storewill not match$.store. This is consistent with JSON's case-sensitive nature. - 零基索引与一基索引:JSONPath使用零基索引(第一个元素是[0]),而XPath使用一基索引(第一个元素是[1])。这是差一错误的常见来源。
- 实现差异:在IETF标准(RFC 9535)之前,JSONPath实现在处理空结果、null值和过滤语法等边缘情况时存在差异。始终使用你特定的库测试表达式。
常见陷阱和技巧
2024年,IETF发布了RFC 9535,正式标准化了JSONPath。该规范解决了各实现之间存在的许多歧义和不一致。标准的关键方面包括:
- < 10)])在大型文档上可能很慢,因为它必须遍历整个树。对于性能关键的应用,尽可能使用更具体的路径。
- 大小写敏感:JSONPath区分大小写。$.Store不会匹配$.store。这与JSON的大小写敏感性质一致。
- 转义特殊字符:如果键名包含点或括号,必须使用带引号的括号表示法:$['key.with.dots']而非$.key.with.dots。
- 无父级遍历:与XPath不同,JSONPath无法导航到父节点。没有XPath的..(父轴)的等效项。JSONPath的..表示递归下降,而非父级。
如果你正在开始一个新项目,优先选择实现RFC 9535的库,以获得最大的兼容性和可预测的行为。
需要快速查询JSON数据?试试我们免费的在线JSONPath测试器,实时针对你的JSON文档评估表达式。
试试 JSONPath 查找器IETF标准:RFC 9535
Python示例
JSONPath是一种JSON查询语言,类似于XPath之于XML。它使用路径表达式来导航和提取JSON文档中的特定值。像$.store.book[0].title这样的JSONPath表达式让你可以在复杂的嵌套JSON结构中精确定位数据,而无需编写自定义解析代码。
什么是JSONPath?
JSONPath专为JSON的对象和数组结构设计,而XPath专为XML的元素和属性树设计。JSONPath使用$作为根节点,点表示法访问对象,括号表示法访问数组。XPath使用/进行路径分隔,@访问属性。JSONPath更简单但功能不如XPath丰富。
JSONPath与XPath有何不同?
JSONPath中的双点(..)是递归下降运算符。它在JSON结构的所有层级搜索命名键,而不仅仅是直接子节点。例如,$..author查找整个文档中任何位置的所有author键,无论它们嵌套多深。
JSONPath中的双点(..)是什么意思?
<、><=、>< 10)]查找所有低于10美元的书籍。
JSONPath可以根据条件过滤数据吗?
JSONPath最初由Stefan Goessner于2007年提出,没有正式规范,导致各实现之间存在差异。2024年,IETF发布了RFC 9535正式标准化JSONPath。现代实现正在趋同于此标准,但一些较旧的库可能仍有轻微的语法差异。