JSONPath คืออะไร?
JSONPath คือภาษาคิวรีสำหรับดึงค่าจากเอกสาร JSON เดิมทีเสนอโดย Stefan Goessner ในปี 2007 เพื่อเป็นตัวเทียบเคียงกับ XPath สำหรับ XML โดย JSONPath ช่วยให้คุณเขียนนิพจน์อย่าง $.store.book[*].author เพื่อเลือกฟิลด์ author ทั้งหมดภายในอาร์เรย์ book โดยไม่ต้องเขียนลูปหรือโค้ดสำรวจด้วยตนเอง ภาษานี้ได้รับการกำหนดมาตรฐานในเดือนกุมภาพันธ์ 2024 ในรูป RFC 9535 ซึ่งเผยแพร่โดย IETF
นิพจน์ JSONPath จะเริ่มต้นด้วย $ เสมอ ซึ่งแทนรากของเอกสาร จากนั้นจึงต่อตัวเลือก: dot notation สำหรับคีย์ออบเจกต์ bracket notation สำหรับดัชนีอาร์เรย์ wildcard สำหรับลูกทุกตัว และตัวดำเนินการ recursive descent (..) สำหรับค้นหาในทุกระดับของการซ้อน นิพจน์ตัวกรองเช่น [?(@.price < 10)] ช่วยให้คุณเลือกองค์ประกอบตามเงื่อนไขที่ประเมินจากค่าของมัน
JSONPath ถูกใช้งานในเครื่องมือทดสอบ API เช่น Postman แพลตฟอร์ม observability เช่น Datadog และ Splunk เครื่องยนต์เวิร์กโฟลว์เช่น Apache NiFi และไลบรารีในภาษาต่าง ๆ ได้แก่ JavaScript, Python, Go, Java และ C# การทดสอบนิพจน์กับข้อมูลจริงก่อนนำไปฝังในโค้ดหรือไฟล์คอนฟิกจะช่วยป้องกันความล้มเหลวที่ตรวจจับได้ยากเมื่อโครงสร้าง JSON ไม่ตรงกับที่คาดไว้
ทำไมต้องใช้เครื่องมือทดสอบ JSONPath นี้?
การเขียนนิพจน์ JSONPath ด้วยมือเป็นเรื่องที่เกิดข้อผิดพลาดได้ง่าย จุด bracket ที่หายไป หรือไวยากรณ์ตัวกรองที่ผิดพลาดอาจทำให้ได้ผลลัพธ์ว่างเปล่าโดยไม่มีข้อความแสดงข้อผิดพลาด เครื่องมือนี้ให้ฟีดแบ็กภาพทันทีว่านิพจน์ของคุณตรงกับอะไร
กรณีการใช้งาน 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 แบบ standalone ที่มีภาษาตัวกรอง Turing-complete เป็นของตนเอง XPath ทำงานบน XML และเป็นส่วนหนึ่งของสแตกข้อกำหนด W3C
| คุณสมบัติ | JSONPath | jq | XPath |
|---|---|---|---|
| รูปแบบข้อมูล | JSON | JSON | XML |
| เข้าถึงองค์ประกอบแรก | $.store.book[0] | .store.book[0] | /store/book[1] |
| การค้นหาแบบ recursive | $..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}