什么是JSONPath?
JSONPath是一种用于从JSON文档中提取值的查询语言。它最初由Stefan Goessner于2007年提出,作为XML的XPath的类比工具,JSONPath允许你编写类似$.store.book[*].author的表达式,无需编写循环或手动遍历代码即可选取book数组中的所有author字段。该语言于2024年2月由IETF发布为RFC 9535正式标准化。
JSONPath表达式始终以$开头,代表文档的根节点。从根节点起,你可以链式添加选择器:使用点符号访问对象键,使用括号符号访问数组索引,使用通配符选取所有子节点,以及使用递归下降操作符(..)搜索嵌套的每一层。过滤表达式如[?(@.price < 10)]可让你根据对元素值求值的条件来选取元素。
JSONPath被广泛应用于Postman等API测试工具、Datadog和Splunk等可观测性平台、Apache NiFi等工作流引擎,以及JavaScript、Python、Go、Java和C#的编程库中。在将表达式嵌入代码或配置文件之前,先针对真实数据进行测试,可以避免JSON结构与预期不符时发生的隐性错误。
为什么使用此JSONPath测试器?
手动编写JSONPath表达式容易出错。一个缺失的点、错误的括号类型或不正确的过滤语法都可能导致返回空结果且没有任何错误提示。此工具为你提供即时的可视化反馈,告诉你表达式匹配了哪些内容。
JSONPath使用场景
JSONPath语法参考
RFC 9535定义了标准的JSONPath语法。下表涵盖了大多数查询中会用到的操作符。所有表达式均以$(根节点)开头,并链式添加一个或多个选择器来导航文档结构。
| 操作符 | 描述 | 示例 |
|---|---|---|
| $ | Root element | $.store |
| .key | Child property | $.store.book |
| [n] | Array index (zero-based) | $.store.book[0] |
| [*] | All elements in array/object | $.store.book[*] |
| .. | Recursive descent | $..author |
| [start:end] | Array slice | $.store.book[0:2] |
| [?()] | Filter expression | $.store.book[?(@.price<10)] |
| @ | Current node (inside filter) | $.store.book[?(@.isbn)] |
JSONPath vs jq vs XPath
JSONPath、jq和XPath针对不同格式和使用场景解决同一个问题(查询结构化数据)。JSONPath面向JSON,可作为库在大多数语言中使用。jq是一个独立的CLI工具,拥有自己的图灵完备过滤语言。XPath面向XML,是W3C规范体系的一部分。
| 特性 | JSONPath | jq | XPath |
|---|---|---|---|
| 数据格式 | JSON | JSON | XML |
| 访问第一个元素 | $.store.book[0] | .store.book[0] | /store/book[1] |
| 递归搜索 | $..price | .. | .price? | //price |
| 过滤表达式 | [?(@.price<10)] | select(.price < 10) | [price<10] |
| 规范 | RFC 9535 | stedolan.github.io | W3C XPath 3.1 |
代码示例
如何在常用语言和工具中求值JSONPath表达式。每个示例均使用相同的书店JSON结构以便对比。
import { JSONPath } from 'jsonpath-plus';
const data = {
store: {
book: [
{ title: 'Moby Dick', price: 8.99 },
{ title: 'The Great Gatsby', price: 12.99 }
]
}
};
// Get all book titles
JSONPath({ path: '$.store.book[*].title', json: data });
// → ['Moby Dick', 'The Great Gatsby']
// Recursive descent — find every price in the document
JSONPath({ path: '$..price', json: data });
// → [8.99, 12.99]
// Filter — books under $10
JSONPath({ path: '$.store.book[?(@.price < 10)]', json: data });
// → [{ title: 'Moby Dick', price: 8.99 }]from jsonpath_ng.ext import parse
import json
data = {
"store": {
"book": [
{"title": "Moby Dick", "price": 8.99},
{"title": "The Great Gatsby", "price": 12.99}
]
}
}
# Get all book titles
expr = parse("$.store.book[*].title")
titles = [match.value for match in expr.find(data)]
# → ['Moby Dick', 'The Great Gatsby']
# Recursive descent — all prices
expr = parse("$..price")
prices = [match.value for match in expr.find(data)]
# → [8.99, 12.99]
# Filter — books under $10
expr = parse("$.store.book[?price < 10]")
cheap = [match.value for match in expr.find(data)]
# → [{"title": "Moby Dick", "price": 8.99}]package main
import (
"fmt"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
)
func main() {
src := `{
"store": {
"book": [
{"title": "Moby Dick", "price": 8.99},
{"title": "The Great Gatsby", "price": 12.99}
]
}
}`
obj, _ := oj.ParseString(src)
// Get all book titles
expr, _ := jp.ParseString("$.store.book[*].title")
results := expr.Get(obj)
fmt.Println(results)
// → [Moby Dick The Great Gatsby]
// Recursive descent — all prices
expr2, _ := jp.ParseString("$..price")
fmt.Println(expr2.Get(obj))
// → [8.99 12.99]
}# jq uses its own syntax, not JSONPath, but solves the same problem.
# Mapping common JSONPath patterns to jq:
# $.store.book[*].title → get all titles
echo '{"store":{"book":[{"title":"Moby Dick"},{"title":"Gatsby"}]}}' | \
jq '.store.book[].title'
# → "Moby Dick"
# → "Gatsby"
# $..price → recursive descent for "price" keys
echo '{"a":{"price":1},"b":{"price":2}}' | \
jq '.. | .price? // empty'
# → 1
# → 2
# Filter: books where price < 10
echo '{"store":{"book":[{"title":"A","price":8},{"title":"B","price":12}]}}' | \
jq '.store.book[] | select(.price < 10)'
# → {"title":"A","price":8}