ما هو JSONPath؟
JSONPath هي لغة استعلام لاستخراج القيم من مستندات JSON. اقترحها أصلًا Stefan Goessner عام 2007 كبديل لـ XPath في XML، وتتيح لك JSONPath كتابة تعبيرات مثل $.store.book[*].author لتحديد جميع حقول المؤلف داخل مصفوفة كتب دون كتابة حلقات أو كود اجتياز يدوي. وقد تم توحيد هذه اللغة في فبراير 2024 كـ RFC 9535 الصادر عن IETF.
يبدأ تعبير JSONPath دائمًا بـ $ الذي يمثل جذر المستند. ومن هناك تتتابع المحددات: الترميز النقطي لمفاتيح الكائنات، والترميز بالأقواس لمؤشرات المصفوفات، وأحرف البدل لجميع العناصر الفرعية، وعامل النزول العودي (..) للبحث في كل مستويات التداخل. كما تتيح لك تعبيرات التصفية مثل [?(@.price < 10)] تحديد العناصر بناءً على شروط تُقيَّم وفق قيمها.
تُستخدم JSONPath في أدوات اختبار API مثل Postman، ومنصات الرصد مثل 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 و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}