JSONPath কোয়েরি ভাষা বাস্তবে
সংক্ষেপে: ২০২৪ সালে JSONPath অবশেষে আনুষ্ঠানিক স্ট্যান্ডার্ড পেল, RFC 9535। এই নিবন্ধে সিনট্যাক্স, ফিল্টার সেমান্টিক্স, ফাংশন ও বাস্তবায়নগুলোর ঐতিহাসিক পার্থক্য ব্যাখ্যা করা হয়েছে।

মূল কথা: আগে স্পষ্ট পাথ দিয়ে লোকেট করুন আর তার উপর ফিল্টার বসান, আর এক্সপ্রেশন RFC 9535 অনুযায়ী লিখুন; একটি নির্দিষ্ট ফিল্ডে প্রপার্টি অ্যাক্সেস বা JSON Pointer, আর অ্যাগ্রিগেশনে jq বা কোড ব্যবহার করুন।
আপনি নিশ্চয়ই
$.store.book[?(@.price < 10)].title-এর মতো এক্সপ্রেশন দেখেছেন, কিন্তু এটি ২০২৪ সালে আন্তর্জাতিক স্ট্যান্ডার্ড হয়।
১. বাস্তব মান থেকে আনুষ্ঠানিক স্ট্যান্ডার্ডে
JSONPath স্টেফান গেসনার ২০০৭ সালে প্রস্তাব করেন, আর দীর্ঘদিন একমাত্র "স্পেসিফিকেশন" ছিল একটি ব্লগ পোস্ট, প্রতিটি ভাষার বাস্তবায়ন নিজের মতো করে:
- ফিল্টারে বন্ধনী দরকার কি না (
?(@.a)বনাম?@.a); - অ্যারে ইনডেক্স
[-1]সমর্থিত কি না; - ফল "মান" ফেরত দেয় নাকি "মান ও পাথ";
- স্ক্রিপ্ট এক্সপ্রেশন (
?(@.price < $.max)) অনুমোদিত কি না।
২০২৪ সালের ফেব্রুয়ারিতে প্রকাশিত RFC 9535 এই বিচ্যুতিগুলোকে একক সিনট্যাক্স ও সেমান্টিক্সে মিলিয়েছে এবং অফিশিয়াল কমপ্লায়েন্স টেস্ট স্যুট দিয়েছে। নতুন প্রকল্প RFC 9535 অনুসরণ করুক; পুরোনো প্রকল্প মাইগ্রেট করার সময় আগে বাস্তবায়নের সংস্করণ নিশ্চিত করুন।
২. সিনট্যাক্স চিট শিট
| সিনট্যাক্স | অর্থ | উদাহরণ |
|---|---|---|
$ |
রুট নোড | $.store |
@ |
ফিল্টারের ভেতরের চলতি নোড | $[?@.price < 10] |
.name |
চাইল্ড নোড (নাম অবশ্যই [A-Za-z_][A-Za-z0-9_]* মেলাতে হবে) |
$.store.book |
['name'] |
চাইল্ড নোড (যেকোনো নাম, নন-ASCII ও ডটসহ) | $['store']['বই'] |
[0] |
অ্যারে ইনডেক্স; ঋণাত্মক সংখ্যা শেষ থেকে গোনে | $.book[0], $.book[-1] |
[0,2] |
একাধিক ইনডেক্স / একাধিক নাম | $.book[0,2] |
[1:3] |
স্লাইস, শুরু অন্তর্ভুক্ত ও শেষ বাদ | $.book[1:3] |
[::2] |
ধাপসহ স্লাইস | $.book[::2] |
[*] |
সব চাইল্ড নোডের ওয়াইল্ডকার্ড | $.book[*].title |
..name |
পুনরাবৃত্ত ডিসেন্ট, যেকোনো গভীরে খোঁজা | $..price |
..[*] |
পুনরাবৃত্তভাবে সব নোড নেওয়া | $..* |
[?<expr>] |
ফিল্টার সিলেক্টর | $.book[?@.price < 10] |
একটি সেমান্টিক্স মনে রাখতেই হবে: JSONPath নোড সেট (নোডলিস্ট) ফেরত দেয়, একক মান নয়। একটি নোডই মিললেও ফল দৈর্ঘ্য একের লিস্ট।
৩. একটি নমুনা দিয়ে সিনট্যাক্স হেঁটে দেখা
{
"store": {
"book": [
{ "title": "JSON নির্ভরযোগ্য গাইড", "price": 8.95, "tags": ["json", "spec"] },
{ "title": "ডেটা বিনিময় ফরম্যাটের ইতিহাস", "price": 12.99, "tags": ["history"] },
{ "title": "পার্সার বাস্তবায়নের মূলনীতি", "price": 8.99, "tags": ["parser", "json"] }
],
"bicycle": { "color": "red", "price": 19.95 }
},
"expensive": 10
}
| এক্সপ্রেশন | ফল |
|---|---|
$.store.bicycle.color |
["red"] |
$..price |
৪টি দাম (সাইকেলের দামসহ) |
$.store.book[*].title |
৩টি শিরোনাম |
$.store.book[?@.price < 10].title |
বই ১ ও ৩-এর শিরোনাম |
$.store.book[?@.tags] |
যেসব বইয়ে tags আছে (অস্তিত্ব পরীক্ষা) |
$.store.book[-1].title |
শেষ বইয়ের শিরোনাম |
$..book[0,1] |
প্রথম দুই বই |
$.store.book[?@.price < $.expensive] |
সিনট্যাক্টিকভাবে বাস্তবায়ন-নির্ভর; RFC 9535 ক্রস-রুট রেফারেন্স অনুমোদন করে না |
শেষ সারি একটি ঐতিহাসিক ফাঁদ: অনেক বাস্তবায়ন ফিল্টারের ভেতরে $.expensive দিয়ে ক্রস-নোড তুলনা করতে দেয়, যা RFC 9535 স্পষ্টভাবে নিষিদ্ধ করে —
একটি ফিল্টার কেবল চলতি নোড @ অ্যাক্সেস করতে পারে। বৈশ্বিক থ্রেশহোল্ডের সঙ্গে তুলনা দরকার হলে থ্রেশহোল্ডটি কোয়েরি ফাংশনে আর্গুমেন্ট হিসেবে দিন।
৪. ফিল্টার কী প্রকাশ করতে পারে
RFC 9535 ফিল্টার এক্সপ্রেশন চার ধরনের গঠন সমর্থন করে:
Logic: && || ! e.g. ?@.price < 10 && @.tags
Comparison: == != < <= > >=
Existence: a bare path e.g. ?@.tags (tests existence)
Functions: length() count() match() search() value()
কয়েকটি ব্যবহারিক উদাহরণ:
// Price between 8 and 10
$.store.book[?@.price >= 8 && @.price <= 10]
// Title contains "JSON" (regex match; the second argument is an I-Regexp)
$.store.book[?match(@.title, ".*JSON.*")]
// More than one tag
$.store.book[?length(@.tags) > 1]
// Get the first matched value instead of a list (value() turns a single-node set into a value)
$.store.book[?@.price < 9].title.value()
match() পুরো-স্ট্রিং মিল, আর search() উপ-স্ট্রিং খোঁজা; অন্তর্নিহিত রেগুলার ডায়ালেক্ট I-Regexp (RFC 9485-এর উপসেট),
যা ব্যাকরেফারেন্স বা lookaround সমর্থন করে না — এটি সীমাবদ্ধতা ও রেগুলার DoS-র বিরুদ্ধে সুবিধাজনক ঢাল, দুটোই।
৫. বাস্তবায়নের পার্থক্য: মাইগ্রেশনের আগে যাচাই করুন
| বাস্তবায়ন | ভাষা | RFC 9535 সমর্থন | নোট |
|---|---|---|---|
jsonpath-plus |
JavaScript | বেশিরভাগ সামঞ্জস্যপূর্ণ | এক্সটেনশন ফাংশন ও স্ক্রিপ্ট এক্সপ্রেশন সমর্থন; বেশি সক্ষম কিন্তু কঠোরভাবে স্ট্যান্ডার্ড নয় |
jsonpath (dchester) |
JavaScript | আংশিক | পুরোনো লাইব্রেরি, ?() সিনট্যাক্স ও ভিন্ন ফল কাঠামো |
ojg / gjson |
Go | ojg-তে ভালো সমর্থন |
gjson অন্য পাথ সিনট্যাক্স ব্যবহার করে, JSONPath নয় |
jsonpath-ng |
Python | আংশিক | এক্সটেনশন সমর্থন করে, বেশ শিথিল |
Jayway JsonPath |
Java | আংশিক | বিস্তৃত ইকোসিস্টেম; ঐতিহাসিক আচরণ RFC থেকে আলাদা |
মাইগ্রেশনের আগে তিন চেক:
- ফিল্টারে বন্ধনী দরকার কি না;
-1-এর মতো ঋণাত্মক ইনডেক্স সমর্থিত কি না;- ফল "মানের অ্যারে" নাকি "
{path, value}অবজেক্টের অ্যারে"।
তৃতীয়টিতে পা পিছলানো সবচেয়ে সহজ: ফ্রন্টএন্ড result[0] পড়ে, আর লাইব্রেরি বদলানোর পর অবজেক্ট পেয়ে পেজ ফাঁকা হয়ে যায়।
৬. পারফরম্যান্স ও নিরাপত্তা
পারফরম্যান্স
- JSONPath মূলত একটি ট্রাভার্সাল, জটিলতা O(নোড সংখ্যা), আর ইনডেক্স ব্যবহার করতে পারে না;
..দিয়ে পুনরাবৃত্ত ডিসেন্ট সবচেয়ে দামি;$.store.bookদিয়ে আগে লোকেট করে তারপর ফিল্টার করতে পারলে$..bookলিখবেন না;- এক্সপ্রেশন একবার কম্পাইল করে পুনরায় ব্যবহার করুন (বেশিরভাগ লাইব্রেরি
compileবা ক্যাশে দেয়), প্রতিটি কোয়েরিতে আবার পার্স করবেন না; - ১,০০,০০০-এর বেশি নোডযুক্ত ডকুমেন্টে আগে JSON Pointer দিয়ে নির্দিষ্ট এক্সট্রাকশন, বা সার্ভারে
jqদিয়ে প্রিপ্রসেসিং ভাবুন।
নিরাপত্তা
- ব্যবহারকারীর ইনপুট থেকে আসা এক্সপ্রেশনে দৈর্ঘ্য ও নেস্টিং গভীরতার সীমা থাকতে হবে;
- JSONPath-কে অনুমোদন সামলাতে দেবেন না — এটি কেবল পড়তে পারে, "কোন ফিল্ড অদৃশ্য" প্রকাশ করতে পারে না;
match()-এর ব্যবহৃত রেগুলার ব্যবহারকারীর কাছ থেকে এলে এর জটিলতা সীমিত করুন; PCRE-এর চেয়ে নিরাপদ হলেও দৈর্ঘ্য ক্যাপ সুপারিশকৃত।
৭. কখন JSONPath ব্যবহার করবেন না
- কেবল একটি নির্দিষ্ট ফিল্ড দরকার → সোজা
data.store.bicycle.colorব্যবহার করুন; - পরিবর্তনের পর ফেরত লিখতে হবে → JSON Pointer বা প্যাচ ব্যবহার করুন; JSONPath ফেরত লেখে না;
- অ্যাগ্রিগেশন (যোগ, গ্রুপিং) দরকার →
jq, SQL, বা কোড ব্যবহার করুন; JSONPath কেবল বাছাই করে; - ডকুমেন্ট বিশাল আর কেবল কয়েকটি ফিল্ড দরকার → পুরো ট্রি আগে মেমরিতে পড়ার বদলে স্ট্রিমিং পার্সার ব্যবহার করুন।
৮. চেকলিস্ট
- [ ] এক্সপ্রেশন RFC 9535 অনুসরণ করে ও ক্রস-রুট রেফারেন্সের উপর নির্ভর করে না;
- [ ] স্পষ্ট যে ফল নোড সেট; একক মান পেতে
[0]বাvalue()ব্যবহার করুন; - [ ] এক্সপ্রেশন আগেই কম্পাইল করা ও পুনরায় ব্যবহৃত;
- [ ] ব্যবহারকারী-নিয়ন্ত্রিত এক্সপ্রেশনে দৈর্ঘ্য ও জটিলতার সীমা আছে;
- [ ] বাস্তবায়ন বদলানোর সময় অফিশিয়াল কমপ্লায়েন্স টেস্ট কেস চালানো হয়।
সাধারণ প্রশ্ন
JSON Pointer ও JSONPath-এর মধ্যে কীভাবে বাছাই করব?
একটি নির্দিষ্ট ফিল্ড দরকার হলে সাধারণ প্রপার্টি অ্যাক্সেস বা JSON Pointer; ফিল্টার, ওয়াইল্ডকার্ড বা পুনরাবৃত্ত ডিসেন্ট দরকার হলে JSONPath। JSONPath কেবল বাছাই করে, কখনো ফেরত লেখে না, তাই ফেরত লেখা JSON Pointer বা প্যাচের কাজ।
ফিল্টার $.expensive রেফারেন্স করতে পারে না কেন?
RFC 9535 স্পষ্টভাবে ক্রস-রুট রেফারেন্স নিষিদ্ধ করে; একটি ফিল্টার কেবল চলতি নোড @ অ্যাক্সেস করতে পারে। এর বদলে থ্রেশহোল্ড কোয়েরি ফাংশনে আর্গুমেন্ট হিসেবে দিন। পুরোনো বাস্তবায়ন সিনট্যাক্স গ্রহণ করে, কিন্তু লাইব্রেরি বদলালে বা আপগ্রেড করলে ভাঙে।
মান পড়তে result[0]-এর উপর নির্ভর করা উচিত?
কেবল নিশ্চিত হওয়ার পর যে চলমান বাস্তবায়ন মানের অ্যারে ফেরত দেয়। কিছু লাইব্রেরি {path, value} অবজেক্ট দেয়, ফলে result[0] অবজেক্ট হয় আর পেজ ফাঁকা হয়ে যায়। একক মান [0] বা value() দিয়ে পড়ুন, আর বাস্তবায়ন বদলানোর আগে অফিশিয়াল কমপ্লায়েন্স স্যুট চালান।