دليل صياغة JSONPath: الاستعلام عن JSON وتصفيته بالأمثلة
JSONPath هي لغة استعلام لـJSON، تمامًا كما أن XPath لغة استعلام لـXML. تكتب تعبير مسار فيُعيد المُقيِّم كل قيمة مطابقة. ولكي تلتقط كل أسماء المؤلفين من مستند متجر كتب، تكتب $.store.book[*].author — فتستعيد قائمة المؤلفين، دون أي شيفرة تنقّل.
يمرّ هذا الدليل بكل مُحدِّد في صياغة JSONPath مع أمثلة جاهزة للنسخ تشغّلها وأنت تقرأ. ولْنحسم أمرًا مبكرًا: هناك لهجتان. لهجة Goessner لعام 2007 هي الكلاسيكية بحكم الأمر الواقع، وRFC 9535 هو المعيار الرسمي من IETF المنشور في فبراير 2024. تتفقان في المسارات الشائعة وتتباعدان في الحالات الحدية، لذا يُنبّه هذا الدليل إلى الفروق كلما ظهرت. يمكنك تجربة كل تعبير أدناه في مُختبِر JSONPath والتبديل بين المحرّكين للمقارنة.
إليك بطاقة المُحدِّدات المرجعية للبدء. تشرح بقية المقال كل صفّ منها بمثال عملي على مستند JSON واحد مشترك.
| المُحدِّد | المعنى | المثال |
|---|---|---|
$ | جذر المستند | $ |
@ | العنصر الحالي (داخل المرشّحات) | [?(@.price < 10)] |
.name / ['name'] | عضو فرعي | $.store.book |
.. | نزول تكراري | $..author |
* | كل العناصر / الأعضاء | $.store.book[*] |
[0] | فهرس المصفوفة | $.store.book[0] |
[start:end:step] | شريحة مصفوفة (نصف-مفتوحة) | $.store.book[0:2] |
[a,b] | اتحاد أسماء / فهارس | $.store.book[0,2] |
[?()] | تعبير تصفية | $.store.book[?(@.price < 10)] |
length() count() match() search() value() | دوال RFC 9535 (داخل المرشّح فقط) | [?length(@.title) > 15] |
ما هي JSONPath؟
JSONPath لغة استعلام تصريحية لانتقاء عُقد من مستند JSON. فبدلًا من كتابة حلقة تجوب الكائنات والمصفوفات، تصف الموضع الذي تريده بمسار، فيُعيد المُقيِّم القيم المطابقة. النموذج الذهني هو ذاته الذي يمنحك إياه XPath مع XML: مسار مكوّن من مُحدِّدات تخطو عبر البنية.
تظهر في كل مكان يلمس فيه المطوّرون JSON. تستخدمها لسحب حقل من استجابة API، وللتأكيد على قيمة في اختبار تكامل، ولعنونة الحقول في إعدادات خطوط الأنابيب لـKubernetes وAWS Step Functions وAzure Logic Apps، ولاستخراج بيانات من JSON كبير أو غير منتظم دون كتابة منطق تنقّل يدويًا.
ملاحظة سريعة عن التاريخ، لأنها تفسّر انقسام اللهجات. اقترح Stefan Goessner صيغة JSONPath عام 2007. انتشرت بسرعة وصارت معيارًا بحكم الأمر الواقع، لكنها لم تُوصَّف رسميًا قطّ — فتباعدت التطبيقات في التفاصيل. سدّ IETF تلك الفجوة في فبراير 2024 بـRFC 9535، أول مواصفة رسمية لـJSONPath. كلتا اللهجتين حيّة اليوم، ولهذا قد يتصرّف التعبير نفسه تصرّفًا مختلفًا بحسب المكتبة التي تشغّله.
قبل أن تبدأ الاستعلام، يساعدك أن تقرأ البنية أولًا. نسّق المُدخل الفوضوي بشكل مقروء عبر مُنسِّق JSON حتى يصير التداخل مرئيًا.
المستند المثال
كل مثال أدناه يعمل على JSON متجر الكتب الكلاسيكي لـGoessner. الصقه مرة واحدة وأعِد استخدامه:
{
"store": {
"book": [
{ "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
{ "title": "Sword of Honour", "author": "Evelyn Waugh", "price": 12.99 },
{ "title": "Moby Dick", "author": "Herman Melville", "price": 8.99 },
{ "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
],
"bicycle": { "color": "red", "price": 19.95 }
}
}
أربعة كتب لكلٍّ منها عنوان ومؤلف وسعر، إضافةً إلى دراجة. ضَع في ذهنك: الأسعار هي 8.95 و12.99 و8.99 و22.99، وهذه هي التي تقود نتائج التصفية لاحقًا.
الجذر والعضو الفرعي والنزول التكراري ($ . ..)
يبدأ كل تعبير من الجذر، ويُكتب $. ومنه تخطو إلى الأعضاء الفرعية بنقطة أو بصياغة الأقواس، وكلتاهما متكافئتان:
$.store.book → مصفوفة الكتب
$['store']['book'] → نتيجة مطابقة
صياغة الأقواس هي ما تحتاجه حين يحوي المفتاح مسافات أو نقاطًا أو محارف خاصة أخرى: $['first name'] تعمل حيث تفشل $.first name.
العامل .. هو النزول التكراري. يبحث في كل مستوى من المستند، لا في الأعضاء المباشرة فحسب، ويجمع كل ما يطابق المُحدِّد التالي على أي عمق:
$..author
→ ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]
متى تستخدم .. ومتى تكتب المسار الكامل
النزول التكراري مريح لكنه غير دقيق. تطابق $..price كل سعر في أي مكان من الشجرة — بما في ذلك store.bicycle.price، الذي ربما لم تكن تريده. حين تعرف الشكل، اكتب المسار صراحةً ليبقى الاستعلام دقيقًا:
$..price → [8.95, 12.99, 8.99, 22.99, 19.95] (يشمل الدراجة)
$.store.book[*].price → [8.95, 12.99, 8.99, 22.99] (الكتب فقط)
احفظ .. للبنى غير المنتظمة أو المجهولة فعلًا. المقايضة هي الراحة مقابل التحكّم: كلما عرفت بياناتك أكثر، ينبغي أن تفضّل المسار الصريح أكثر.
المحارف البديلة والفهارس وشرائح المصفوفة (* [0] [start:end:step])
المحرف البديل * ينتقي كل عناصر المصفوفة أو كل أعضاء الكائن:
$.store.book[*].title
→ ["Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"]
فهارس المصفوفة تبدأ من الصفر، والفهارس السالبة تُعدّ من النهاية:
$.store.book[0].title → ["Sayings of the Century"]
$.store.book[-1].title → ["The Lord of the Rings"]
تستخدم الشرائح اصطلاح [start:end:step] نصف-المفتوح نفسه المتبع في Python وJavaScript: start مُضمَّنة وend مستثناة.
$.store.book[0:2].title → ["Sayings of the Century", "Sword of Honour"]
هذا يُعيد كتابَين اثنين، لا ثلاثة — الفهرس 0 والفهرس 1، مع التوقّف قبل الفهرس 2. الحدّ الأعلى المستثنى هو أكثر أخطاء JSONPath شيوعًا على الإطلاق، فيستحق أن تطبعه في ذاكرتك:
[0:2] → أول عنصرين (الفهرسان 0، 1) ← صحيح
[0:3] → أول ثلاثة عناصر (الفهارس 0، 1، 2)
احذف حدًّا لتصل إلى الطرف، وأضِف خطوة لكل عنصر نوني:
$.store.book[2:].title → ["Moby Dick", "The Lord of the Rings"]
$.store.book[:3].title → أول ثلاثة عناوين
$.store.book[::2].title → ["Sayings of the Century", "Moby Dick"] (كل عنصر آخر)
تعبيرات التصفية [?()]
المرشّحات هي ما تستعمله JSONPath أكثر ما تستعمل في العمل اليومي. المرشّح [?()] يُبقي فقط العناصر التي تتحقّق لها مقولة صادقة، وداخل المرشّح يشير @ إلى العنصر الحالي قيد الاختبار.
لانتقاء الكتب الأرخص من 10:
$.store.book[?(@.price < 10)].title
→ ["Sayings of the Century", "Moby Dick"]
في مقابل أسعار متجر الكتب (8.95 و12.99 و8.99 و22.99)، يتأهّل كتابان. وإليك كيف تبني مقولات المرشّح خطوة بخطوة:
- قارن بقيمة حرفية. استخدم
==و!=و<و<=و>و>=— مثل@.price > 10. - طابق سلسلة نصية. السلاسل الحرفية تأخذ علامتي اقتباس مفردتين:
@.author == 'Nigel Rees'. - اختبر الوجود. الإشارة المجرّدة إلى عضو تنتقي العناصر التي تملكه:
[?(@.isbn)]تُبقي فقط الكتب التي لهاisbn. - اجمع الشروط. اربط المقولات بـ
&&و||:[?(@.price < 10 && @.author == 'Herman Melville')].
أكثر أخطاء المرشّح تكرارًا هو نطاق @. داخل المقولة، العنصر الحالي هو @ وليس $. كتابة $.price تشير إلى جذر المستند، لا إلى الكتاب قيد الاختبار:
$.store.book[?($.price < 10)] → نطاق خاطئ، لا يطابق شيئًا مفيدًا
$.store.book[?(@.price < 10)] → صحيح: سعر كل كتاب نفسه
RFC 9535 مقابل الكلاسيكي في المرشّحات
تفترق اللهجتان عند المسافات البيضاء والاقتباس. الكلاسيكي متساهل — [?(@.price<10)] بلا مسافات يُحلَّل بلا مشكلة. أما RFC 9535 فيتّبع قواعده النحوية بدقّة وهو أكثر صرامة في كيفية كتابة المرشّح. إن فشل مرشّح كان يعمل في مكان آخر، فافحص المسافات والمحرّك. أبقِ المرشّحات نظيفة (عوامل مفصولة بمسافات، سلاسل بعلامتَي اقتباس مفردتين) فتُقيَّم بالطريقة نفسها بصرف النظر عن المكتبة التي تشغّلها في النهاية.
مُحدِّدات الاتحاد — انتقاء عدة مفاتيح دفعة واحدة ([a,b])
مُحدِّد الاتحاد يدرج عدة أسماء أو فهارس داخل قوس واحد ويجمعها كلها:
$.store.book[0]['title','author']
→ ["Sayings of the Century", "Nigel Rees"]
تعمل الاتحادات مع الفهارس أيضًا، ويمكنك مزجها مع مُحدِّدات أخرى للحصول على إسقاط ثابت:
$.store.book[0,2].title → ["Sayings of the Century", "Moby Dick"]
$.store.book[*]['title','price'] → عنوان وسعر كل كتاب
الاتحادات هي الأداة الصحيحة حين تريد بضعة حقول محدّدة بدلًا من كائن كامل أو كنس بمحرف بديل.
دوال RFC 9535: length و count و match و search و value
يُعرّف RFC 9535 خمس امتدادات دوال قياسية. وثمة قاعدة واحدة كثيرًا ما تُغفَل، وتقع فيها أدلّة أخرى:
هذه الدوال لا يمكن استدعاؤها إلا داخل مرشّح
[?...]، ولا تصلح أبدًا كمقطع مسار مستقلّ.
كتابة $.store.book.length() غير صالحة في RFC 9535، والقواعد النحوية المعيارية ترفضها. صيغة الاستدعاء كمقطع تلك هي امتداد من jsonpath-plus، وليست جزءًا من المواصفة. لكي تُصفّي حسب الطول، تستدعي الدالة داخل المقولة:
$.store.book[?length(@.title) > 15]
→ [
{ "title": "Sayings of the Century", "author": "Nigel Rees", "price": 8.95 },
{ "title": "The Lord of the Rings", "author": "J. R. R. Tolkien", "price": 22.99 }
]
كلا العنوانين المنتقَيين أطول من 15 محرفًا؛ بينما يُستثنى “Moby Dick” (9) و”Sword of Honour” (15، وليس أكثر من 15).
وإليك ما تفعله كل دالة داخل مرشّح:
length()— طول سلسلة أو مصفوفة أو كائن:[?length(@.title) > 15]count()— عدد العُقد في قائمة عُقد:[?(count(@.authors) > 1)]match()— اختبار تعبير نمطي على السلسلة كاملةً (نمط I-Regexp):[?match(@.author, 'J.*')]search()— اختبار تعبير نمطي على سلسلة فرعية:[?search(@.title, 'the')]value()— يحوّل قائمة عُقد ذات عقدة واحدة إلى قيمتها للمقارنة
الدوال الخمس كلها سمة من سمات RFC 9535. اللهجة الكلاسيكية (Goessner) لا تطبّقها، فإن فشل تعبير قائم على دالة، تأكّد أنك تستدعيها داخل مرشّح وأن محرّكك مضبوط على RFC 9535.
RFC 9535 مقابل Goessner الكلاسيكي — لماذا يختلف التعبير نفسه
حين يُعيد تعبير JSONPath نتائج مختلفة في أداتين، تكون اللهجة هي السبب عادةً. وإليك كيف تتقارن الاثنتان:
| الجانب | Goessner الكلاسيكي (2007) | RFC 9535 (2024) |
|---|---|---|
| التقييس | بحكم الأمر الواقع، لم يُرسَّم قطّ | أول مواصفة رسمية من IETF |
| مسافات/اقتباس المرشّح | متساهل ([?(@.price<10)] مقبول) | صارم، يتبع القواعد النحوية بدقّة |
| مقارنة العضو المفقود | محدَّدة بحسب التطبيق | محدَّدة جيدًا، لا تُطلق خطأً |
| الدوال القياسية | ليست جزءًا من اللهجة | length count match search value |
| المسارات المُسوّاة | لا صيغة قانونية | قانونية، صيغة أقواس بعلامتَي اقتباس مفردتين |
| ترتيب الاتحاد | يتباين بحسب المكتبة | محدَّد |
نصيحة عملية: إن كان نظامك النهائي يعلن التوافق مع RFC 9535، فاكتب وتحقّق في مقابل المحرّك المعياري. وإن كنت تصون تعبيرًا منسوخًا من jsonpath.com أو jsonpath-plus أو خدمة قائمة على Jayway، فاستخدم الكلاسيكي ليتكرّر الناتج. يشغّل مُختبِر JSONPath كلا المحرّكين خلف مفتاح واحد، فتلصق تعبيرًا مرة واحدة وترى كيف تعالجه كل لهجة جنبًا إلى جنب. هذه المقارنة المتجاورة هي أسرع طريقة لمعرفة سبب اختلاف ناتجك.
JSONPath مقابل XPath مقابل jq — أيّها تستخدم
يُخلَط بين هذه الثلاثة، فإليك النسخة المختصرة:
- JSONPath استعلام مسار تصريحي لـJSON. الأفضل تضمينه في الإعدادات وتأكيدات الاختبار حيث تريد تسمية موضع دون كتابة شيفرة.
- XPath هو المُكافئ في عالم XML. استعارت JSONPath بعض رموزه (
*و..و[])، ولهذا تصحّ المماثلة، لكن اللغتين غير قابلتين للتبادل ومجموعتا الدوال مختلفتان. - jq مُعالِج JSON من سطر الأوامر. يتجاوز انتقاء المسار بكثير، إذ يحوّل البيانات ويجمّعها ويعيد تشكيلها، ويعيش في خط أنابيب الصدفة لديك.
القرار عادةً واضح. لتأكيد مضمَّن أو حقل إعدادات في خط أنابيب، توجَّه إلى JSONPath. وللتحويل ومعالجة البيانات عبر الصدفة، توجَّه إلى jq — يغطّي مرجع أوامر jq ذلك المسار بعمق. وحين يكون السؤال هو هل تطابق الحمولة شكلًا متوقعًا بدلًا من أين يقع حقل، تحقّق منها بـمُدقِّق مخطط JSON ودليل التحقّق الكامل الخاص به.
7 أخطاء شائعة في JSONPath
- نسيان الجذر
$. ترفض معظم المحرّكاتstore.book؛ كل تعبير يبدأ بـ$. - خطأ بمقدار واحد في الشريحة.
[0:2]عنصران لا ثلاثة — الحدّ الأعلى مستثنى. - اللهجة الخاطئة. تشغيل تعبير كلاسيكي تحت RFC 9535 (أو العكس) قد يُطلق خطأ تحليل أو يطابق عُقدًا مختلفة. بدّل المحرّك ليناسبه.
- استدعاء الدالة كمقطع مستقلّ.
$.store.book.length()غير صالحة في RFC 9535؛ استدعِlength()داخل مرشّح. - نسيان
@داخل المرشّح.[?($.price < 10)]تشير إلى الجذر؛ استخدم[?(@.price < 10)]. - اقتباس خاطئ في الأقواس.
$[store]خطأ؛ ضع المفتاح بين علامتَي اقتباس:$['store']. - افتراض أن
..يتوقّف عند المستوى الأول. النزول التكراري يطابق على كل عمق، لا الأعضاء المباشرة فحسب.
اختبر JSONPath عبر الإنترنت، بخصوصية
أسرع طريقة لتعلّم صياغة JSONPath هي تشغيلها. يُقيّم مُختبِر JSONPath كل تعبير في هذا الدليل مباشرةً: محرّكا RFC 9535 والكلاسيكي معًا، وعروض نتائج القيم / المسارات / الاثنين، ومسارات مُسوّاة لتصحيح الأخطاء. يعمل التقييم بالكامل داخل المتصفح، بلا رفع للبيانات ولا تسجيل ولا استدعاء eval، فهو آمن للحمولات المملوكة. ابنِ مسارًا هنا، وتأكّد أنه ينتقي العُقد التي تريدها بالضبط، ثم الصق التعبير المتحقَّق منه مباشرةً في شيفرتك أو اختباراتك أو خط أنابيبك.
ولبقية سير عمل JSON، حوّل استجابة نموذجية إلى واجهات مكتوبة الأنواع بـJSON إلى TypeScript، أو قارن مستندَين حقلًا حقلًا بـمقارنة JSON.
الأسئلة الشائعة
فيمَ تُستخدم JSONPath؟
تستعلم JSONPath عن JSON دون شيفرة أمرية. يستخدمها المطوّرون لسحب حقول من استجابات API، والتأكيد على قيم في اختبارات التكامل، وعنونة الحقول في إعدادات Kubernetes وAWS Step Functions وAzure Logic Apps. تتألّق في استخراج البيانات من بنى كبيرة أو غير منتظمة حيث تكون كتابة التنقّل يدويًا مُمِلّة.
ما الفرق بين RFC 9535 وJSONPath الكلاسيكي؟
الكلاسيكي هو لهجة Stefan Goessner لعام 2007 بحكم الأمر الواقع — واسعة التطبيق لكنها لم تُوصَّف رسميًا قطّ، فتباعدت المكتبات. أما RFC 9535 فهو مواصفة IETF الرسمية لفبراير 2024: يُعرّف قواعد نحوية دقيقة، ومسارات مُسوّاة للنتائج، وخمس دوال قياسية. يختلف الاثنان عند الأطراف في المرشّحات والاتحادات ومقارنة العضو المفقود.
كيف تعمل تعبيرات تصفية JSONPath؟
المرشّح [?()] يُبقي فقط العناصر التي تكون مقولتها صادقة، و@ هو العنصر الحالي. مثلًا، $.store.book[?(@.price < 10)] تنتقي الكتب التي يقلّ سعرها عن 10. يمكنك جمع الشروط بـ&& و||، واختبار ما إذا كان عضو موجودًا، والمقارنة بقيم حرفية نصية أو رقمية.
هل يمكنني استخدام length() بصيغة $.store.book.length()؟
لا. في RFC 9535، لا يمكن استدعاء length() والدوال الأربع الأخرى إلا داخل مرشّح، مثل $.store.book[?length(@.title) > 15]. صيغة المقطع المستقلّ $.store.book.length() هي امتداد من jsonpath-plus، وليست JSONPath معياريًا، وقواعد RFC 9535 النحوية ترفضها.
هل JSONPath مماثلة لـXPath؟
لا، لكن الفكرة متشابهة. يستعلم XPath عن XML؛ وتستعلم JSONPath عن JSON؛ وكلاهما يحدّد العُقد بمُحدِّدات مسار. استعارت JSONPath عمدًا بعض رموز XPath — * و.. و[] — مما يجعل المماثلة مفيدة، لكن الصياغة والدلالات ومجموعات الدوال مختلفة وغير قابلة للتبادل.
ماذا يفعل النزول التكراري (..) في JSONPath؟
العامل .. يبحث في كل مستوى من المستند، لا الأعضاء المباشرة فحسب. تجمع $..author كل عضو مؤلف أينما ظهر، على أي عمق تداخل. وهي أسرع طريقة لسحب حقل واحد من بنية عميقة التداخل أو غير منتظمة، لكنها قد تطابق عُقدًا أكثر بكثير مما تتوقّع — ضيّقها متى استطعت.