JSONPath là gì?
JSONPath là ngôn ngữ truy vấn dùng để trích xuất các giá trị từ tài liệu JSON. Ban đầu được Stefan Goessner đề xuất vào năm 2007 như một tương đương của XPath dành cho XML, JSONPath cho phép bạn viết các biểu thức như $.store.book[*].author để chọn tất cả các trường author bên trong một mảng book mà không cần viết vòng lặp hay mã duyệt thủ công. Ngôn ngữ này được chuẩn hóa vào tháng 2 năm 2024 theo RFC 9535, được công bố bởi IETF.
Một biểu thức JSONPath luôn bắt đầu bằng $ đại diện cho gốc của tài liệu. Từ đó bạn nối các bộ chọn: ký hiệu dấu chấm cho khóa đối tượng, ký hiệu ngoặc vuông cho chỉ số mảng, ký tự đại diện cho tất cả phần tử con, và toán tử đệ quy (..) để tìm kiếm qua mọi cấp độ lồng nhau. Biểu thức lọc như [?(@.price < 10)] cho phép chọn các phần tử dựa trên điều kiện đánh giá theo giá trị của chúng.
JSONPath được sử dụng trong các công cụ kiểm thử API như Postman, các nền tảng quan sát như Datadog và Splunk, các engine quy trình làm việc như Apache NiFi, và các thư viện lập trình trên JavaScript, Python, Go, Java và C#. Việc kiểm tra biểu thức với dữ liệu thực trước khi nhúng vào code hay file cấu hình giúp tránh lỗi âm thầm khi cấu trúc JSON không khớp với giả định của bạn.
Tại sao dùng công cụ kiểm tra JSONPath này?
Viết biểu thức JSONPath bằng tay rất dễ mắc lỗi. Một dấu chấm bị thiếu, loại ngoặc sai, hoặc cú pháp bộ lọc không đúng có thể trả về kết quả trống mà không có thông báo lỗi. Công cụ này cung cấp phản hồi trực quan tức thì về những gì biểu thức của bạn khớp.
Các trường hợp sử dụng JSONPath
Tài liệu tham khảo cú pháp JSONPath
RFC 9535 định nghĩa cú pháp JSONPath chuẩn. Bảng dưới đây bao gồm các toán tử bạn sẽ sử dụng trong hầu hết các truy vấn. Tất cả biểu thức bắt đầu bằng $ (node gốc) và nối một hoặc nhiều bộ chọn để điều hướng cấu trúc tài liệu.
| Toán tử | Mô tả | Ví dụ |
|---|---|---|
| $ | 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 so với jq so với XPath
JSONPath, jq và XPath giải quyết cùng một vấn đề (truy vấn dữ liệu có cấu trúc) cho các định dạng và trường hợp sử dụng khác nhau. JSONPath nhắm vào JSON và có thể dùng như thư viện trong hầu hết các ngôn ngữ. jq là công cụ CLI độc lập với ngôn ngữ lọc Turing-complete riêng. XPath hoạt động trên XML và là một phần của bộ đặc tả W3C.
| Tính năng | JSONPath | jq | XPath |
|---|---|---|---|
| Định dạng dữ liệu | JSON | JSON | XML |
| Truy cập phần tử đầu tiên | $.store.book[0] | .store.book[0] | /store/book[1] |
| Tìm kiếm đệ quy | $..price | .. | .price? | //price |
| Biểu thức lọc | [?(@.price<10)] | select(.price < 10) | [price<10] |
| Đặc tả | RFC 9535 | stedolan.github.io | W3C XPath 3.1 |
Ví dụ code
Cách đánh giá biểu thức JSONPath trong các ngôn ngữ và công cụ phổ biến. Mỗi ví dụ sử dụng cùng cấu trúc JSON bookstore để so sánh.
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}