Що таке JSONPath?
JSONPath — це мова запитів для вилучення значень з JSON-документів. Спочатку запропонований Стефаном Гесснером у 2007 році як аналог XPath для XML, JSONPath дозволяє писати вирази на зразок $.store.book[*].author для вибору всіх полів автора в масиві book без написання циклів або коду ручного обходу. Мова була стандартизована у лютому 2024 року як RFC 9535, опублікований IETF.
JSONPath-вираз завжди починається з $, що позначає корінь документа. Далі ланцюжком йдуть селектори: крапкова нотація для ключів об'єкта, дужкова нотація для індексів масиву, символи підстановки для всіх дочірніх елементів і оператор рекурсивного спуску (..) для пошуку на кожному рівні вкладеності. Вирази фільтрації на зразок [?(@.price < 10)] дозволяють вибирати елементи на основі умов, що оцінюються щодо їхніх значень.
JSONPath використовується в інструментах тестування API, таких як Postman, платформах спостережуваності на зразок Datadog і Splunk, рушіях робочих процесів, як-от Apache NiFi, а також у програмних бібліотеках для JavaScript, Python, Go, Java та C#. Тестування виразів на реальних даних перед їхнім вбудовуванням у код або конфігураційні файли запобігає прихованим збоям, коли структура JSON не відповідає вашим припущенням.
Чому варто використовувати цей JSONPath Tester?
Написання 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 проти jq проти 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}