JSONPath Filters विस्तार से: Recursion, Slices और Multi-Condition Matching
मुख्य बात: Recursive descent, slices और multi-condition filters वहीं हैं जहाँ JSONPath शक्तिशाली और दुरुपयोग किया गया बनता है। इसमें filter semantics, result आकार और library अंतर शामिल हैं।

सारांश: पहले ज्ञात गहराई पर locate करें, फिर slices और filters जोड़ें, $.. की ओर हाथ न बढ़ाएँ; filter में नंगा पथ केवल अस्तित्व जाँचता है, इसलिए boolean के लिए @.stock == true लिखें।
$.store.book[0].titleकोई भी लिख सकता है। JSONPath को वास्तव में वहाँ तोड़ता है जहाँ$..को slices और कई शर्तों के साथ मिलाया जाता है, और फिर बहुत अधिक परिणाम, बहुत कम परिणाम, या ऐसा क्रम मिलता है जिसकी आपने अपेक्षा नहीं की थी।
1. एक नमूना दस्तावेज़
{
"store": {
"book": [
{ "title": "JSON: The Definitive Guide", "price": 8.95, "stock": true, "tags": ["json", "spec"] },
{ "title": "A History of Data Formats", "price": 12.99, "stock": false, "tags": ["history"] },
{ "title": "How Parsers Work", "price": 8.99, "stock": true, "tags": ["parser", "json"] },
{ "title": "Streaming JSON in Practice", "price": 9.5, "stock": true, "tags": [] }
],
"bicycle": { "color": "red", "price": 19.95 }
},
"updatedAt": "2026-10-31"
}
नीचे का हर व्यंजक इसी दस्तावेज़ को इनपुट मानता है। दो पूर्वाग्रह ध्यान में रखें: परिणाम एक node set है, और उसका क्रम दस्तावेज़ के क्रम का पालन करता है, शर्तें किस क्रम में लिखी गईं उसका नहीं। Filter के अंदर @ केवल वर्तमान उम्मीदवार node को संदर्भित करता है।
2. Recursive descent $..: सुविधाजनक पर महँगा
.. का अर्थ है "वर्तमान स्थिति से किसी भी गहराई पर पुनरावर्ती रूप से खोजें"। RFC 9535 इसे descendant segment कहता है, और इसे names, wildcards, indices, slices और filters के साथ जोड़ा जा सकता है:
$..price // किसी भी गहराई पर price नाम का हर सदस्य
$..book[?(@.price < 10)] // किसी भी गहराई की कोई भी book, फिर filter
$..[?(@.stock)] // किसी भी गहराई का कोई भी node जहाँ stock मौजूद है
तीन बातें समझें:
$..bookbookनाम का हर node ढूँढ़ता है, उसकी गहराई चाहे कुछ भी हो;- Traversal depth-first है, और आउटपुट क्रम इस पर निर्भर है कि implementation children को कैसे visit करती है;
..कोई index नहीं ले सकता, इसलिए इसकी लागत nodes की कुल संख्या के साथ बढ़ती है।
नतीजतन, $..price और $.store.book[*].price अपने आउटपुट में केवल bicycle की कीमत से अलग हो सकते हैं, फिर भी लागत में परिमाण के क्रम से अलग होते हैं। जब आपको गहराई पता हो, हमेशा स्पष्ट पथ लिखें। अगर आप केवल दोनों पथ-भाषाओं की सीमाओं की तुलना करना चाहते हैं, तो JSONPath बनाम JSON Pointer देखें।
3. Array Slices और Wildcards: तीन जाल
Slice सिंटैक्स [start:end:step] है, start शामिल और end शामिल नहीं, ऋणात्मक संख्याएँ अंत से गिनती हैं:
| व्यंजक | अर्थ | परिणाम (चार books के साथ) |
|---|---|---|
[0:2] |
पहली दो books | Book 1 और 2 |
[1:] |
दूसरी book से अंत तक | Book 2, 3 और 4 |
[:2] |
पहली दो books | Book 1 और 2 |
[-2:] |
अंतिम दो books | Book 3 और 4 |
[::2] |
हर दूसरी book | Book 1 और 3 |
[::-1] |
उल्टा क्रम | Book 4, 3, 2 और 1 |
तीन जाल:
- शून्य step अमान्य है। RFC 9535 शून्येतर step की अपेक्षा करता है, जबकि कुछ implementations चुपचाप खाली समूह लौटा देती हैं;
- ऋणात्मक steps सर्वत्र समर्थित नहीं हैं। मूल Goessner implementation इन्हें समर्थित करती है, पर कुछ न्यूनतम implementations केवल धनात्मक steps स्वीकार करती हैं;
- Slice के बाद filtering दायरा नहीं बढ़ाती।
[0:2][?(@.stock)]केवल slice से मिली दो books को filter करता है; यह पूरे array पर वापस नहीं जाता।
Wildcard [*] को slices के साथ जोड़ा जा सकता है, लेकिन [*] "सभी child nodes" देता है। अगर वे children objects हैं, तो .title जोड़ने पर सभी titles मिलती हैं। अगर कोई child स्वयं array है, तो .title एक और स्तर स्वतः flatten नहीं करेगा। विस्तार करना हो तो फिर [*] लिखें; implicit flattening की अपेक्षा न करें।
4. Multi-Condition Filters: Comparisons, Logic और Existence
Filter [?(...)] में चार तरह की रचनाएँ हो सकती हैं: comparisons, logical operators, existence tests और functions। एक आम multi-condition क्वेरी ऐसी दिखती है:
$..book[?(@.price < 10 && @.stock)]
इसका अर्थ है "कीमत 10 से कम और stock truthy है"। कई semantic बारीकियों पर ध्यान दें:
@.stockएक existence test है, "मान true है" नहीं। अगर फ़ील्ड मौजूद है पर उसका मानfalseहै, तो शर्त फिर भी पूरी होती है। Boolean मान जाँचने के लिए@.stock == trueलिखना होगा;&&का बंधन||से कसा है। इन्हें मिलाते समय कोष्ठक जोड़ें, स्मृति पर भरोसा न करें;- तुलना के दोनों पक्षों का type एक ही होना चाहिए। संख्याएँ और स्ट्रिंग्स implicitness से परिवर्तित नहीं होतीं;
@.price < "10"सख्त implementations में false या error है; - गायब फ़ील्ड हमेशा तुलना को false बनाता है।
@.missing < 10error नहीं उठाता; यह बस कुछ भी match नहीं करता।
Filters को JavaScript की तरह लिखने में यहीं सबसे आसानी से फिसलन होती है: if (obj.stock) JavaScript में truthiness जाँचता है, जबकि JSONPath अस्तित्व जाँचता है। पूरे मानक सिंटैक्स और functions के लिए व्यवहार में JSONPath क्वेरी भाषा दोबारा पढ़ें।
5. Script Expressions: अधिक शक्ति, कम portability
मूल Goessner implementation और jsonpath-plus [?(@.price < $.max)] जैसी cross-node तुलनाएँ, और मनमाने scripts भी, अनुमति देती हैं। RFC 9535 स्पष्ट रूप से केवल वर्तमान node @ तक पहुँच की अनुमति देता है और filter के अंदर $ को मना करता है।
// पुरानी implementations में काम करता है; RFC 9535 में अनुमत नहीं
$..book[?(@.price < $.expensive)]
// मानक तरीका: threshold caller से पास करें
query(document, '$..book[?(@.price < threshold)]', { threshold: 10 })
Script expressions तीन समस्याएँ पैदा करते हैं:
- Portability शून्य तक गिर जाती है। Library बदलने का अर्थ है हर व्यंजक फिर से लिखना;
- Security सीमा गायब हो जाती है। जब व्यंजक उपयोगकर्ताओं से आते हैं, script evaluation कोड execution सौंप देता है;
- Static optimization असंभव हो जाती है। Implementation node-by-node interpret ही कर सकती है, जो धीमा है और cache करना कठिन।
अगर आपको सचमुच cross-node तुलना चाहिए, तो threshold को query parameter के रूप में पास करें और application code में दूसरी बार filter करें। यह सामान्यतः किसी extension सिंटैक्स पर निर्भर रहने से अधिक स्थिर है।
6. Result आकार: आप सोचते हैं मान मिलेंगे, पर nodes मिलते हैं
एक ही व्यंजक libraries के बीच कम से कम तीन आकार लौटा सकता है:
["JSON: The Definitive Guide", "How Parsers Work", "Streaming JSON in Practice"] // मानों का array
[{ "path": "$['store']['book'][0]['title']", "value": "..." }] // path + value
[{ "pointer": "/store/book/0/title", "value": "..." }] // JSON Pointer + value
यह तीन चीज़ों को सीधे प्रभावित करता है: frontend परिणाम कैसे पढ़ता है, परिणामों को कैसे deduplicate किया जाता है, और क्वेरी परिणाम कैसे write operations बनते हैं। RFC 9535 द्वारा परिभाषित nodelist node references का समूह है, लेकिन JavaScript implementations सामान्यतः केवल मान लौटाती हैं क्योंकि references process सीमाओं के पार नहीं जा सकतीं।
व्यावहारिक सलाह:
- एकल मान हमेशा
[0]याvalue()के ज़रिए पढ़ें; कभी न मानें कि परिणाम केवल scalar है; - जब वापस लिखना हो, ऐसी implementation चुनें जो JSON Pointers लौटाए या pointer स्वयं बनाएँ;
- खाली परिणाम और
[null]परिणाम अलग हैं; truthiness के बजाय लंबाई जाँचें।
7. Library अंतर: migrate करने से पहले compliance suite चलाएँ
| क्षमता | मूल Goessner | jsonpath-plus | सख्त RFC 9535 |
|---|---|---|---|
Filter कोष्ठक ?() |
आवश्यक | संगत | वैकल्पिक |
Cross-root संदर्भ $.x |
समर्थित | समर्थित | वर्जित |
| Script expressions | समर्थित | समर्थित | वर्जित |
| ऋणात्मक indices और steps | समर्थित | समर्थित | समर्थित |
| Return paths | Configurable | Configurable | निर्दिष्ट नहीं |
length() जैसे functions |
कोई नहीं | Extensions | मानक functions |
Migrate करने से पहले कम से कम चार बातें जाँचें: कोष्ठक आवश्यक हैं या नहीं, filter के अंदर $ आ सकता है या नहीं, ऋणात्मक steps कैसे व्यवहार करते हैं, और result संरचना क्या है। आधिकारिक compliance test suite अधिकांश सिंटैक्स अंतरों को कवर करता है और "library बदलने के बाद तीन पंक्तियाँ गायब हो गईं" जैसी चुपचाप विफलताओं को रोकता है।
8. Performance और दुरुपयोग checklist
Performance एक वाक्य में सिमटती है: JSONPath एक पूर्ण traversal है, और .. सबसे महँगा पूर्ण traversal है।
- 100,000 से अधिक nodes वाले दस्तावेज़ों के लिए पहले स्पष्ट पथ से दायरा सीमित करें, फिर उपसमुच्चय को filter करें;
- व्यंजक एक बार compile करें और दोबारा उपयोग करें; loop के अंदर स्ट्रिंग फिर से parse न करें;
- Aggregation (summing, grouping, sorting) के लिए
jqया कोड पर switch करें; JSONPath केवल select करता है; - शर्तें जितनी जल्दी हो सके रखें:
$.store.book[?(@.price < 10)]अप्रासंगिक nodes को$..[?(@.price < 10)]से कहीं कम scan करता है।
दुरुपयोग checklist:
- [ ] Existence test को boolean truth test मानना;
- [ ] ज्ञात गहराई के बजाय
$..इस्तेमाल करना; - [ ] Cross-node तुलना के लिए script expressions पर निर्भर रहना;
- [ ] यह मानना कि परिणाम हमेशा मानों का array है;
- [ ] उपयोगकर्ता-नियंत्रित व्यंजकों को लंबाई और nesting सीमा के बिना अनुमति देना।
FAQ
जब फ़ील्ड false है तो भी @.stock match क्यों करता है?
क्योंकि filter में नंगे पथ का अर्थ "फ़ील्ड मौजूद है" है, "मान true है" नहीं। Boolean जाँचने के लिए @.stock == true से स्पष्ट तुलना करें। यह JSONPath और JavaScript truthiness के बीच सबसे बड़ा semantic अंतर है।
क्या recursive descent का क्रम स्थिर है?
नहीं। RFC 9535 node-set semantics निर्दिष्ट करता है, और traversal क्रम इस पर निर्भर है कि implementation children को कैसे visit करती है। जब निश्चित क्रम चाहिए, array locate करके index से पढ़ें, या परिणामों को application code में क्रमबद्ध करें।
क्या slices और filters जोड़े जा सकते हैं?
हाँ, पर दायरा याद रखें। $.store.book[0:2][?(@.stock)] केवल slice से मिली दो books को filter करता है। पहले filter और फिर slice करना हो तो $.store.book[?(@.stock)][0:2] लिखें; अलग क्रम अलग परिणाम देता है।
मानक द्वारा मना होने पर भी jsonpath-plus $.expensive क्यों समर्थित करता है?
क्योंकि filters केवल वर्तमान node @ के संदर्भ में परिभाषित हैं। Cross-root संदर्भ व्यंजक को पूरे दस्तावेज़ संदर्भ पर निर्भर बना देता है, जिसे static रूप से विश्लेषित करना और भाषाओं के पार port करना कठिन है। Threshold को व्यंजक में embed करने के बजाय parameter के रूप में पास करें।
Recursive descent को अप्रासंगिक nodes scan करने से कैसे रोकूँ?
जब भी गहराई पता हो स्पष्ट पथ लिखें, पहले दायरा सीमित करें, और ज्ञात स्तर के बदले $.. इस्तेमाल करने के बजाय केवल उपसमुच्चय filter करें। शर्तें जितनी जल्दी हो सके रखें: $.store.book[?(@.price < 10)] अप्रासंगिक nodes को $..[?(@.price < 10)] से कहीं कम scan करता है। 100,000 से अधिक nodes वाले दस्तावेज़ों के लिए यह पहले करें।