শিক্ষানবিশদের জন্য JMESPath: JSONPath-কে হারানো প্রজেকশন কোয়েরি
সংক্ষেপে: JMESPath অবজেক্টের ব্যাচ JSONPath-এর চেয়ে বেশি সরাসরি পুনরাকৃতি দেয়। এই শিক্ষানবিশ গাইডে সিনট্যাক্স, প্রজেকশন, পাইপ, বিল্ট-ইন ফাংশন ও AWS CLI ব্যবহার।

মূল কথা: অবস্থান দিয়ে লোকেট করতে JSON Pointer, ফিল্টার বা পুনরাবৃত্তিতে JSONPath, আর ফল পুনরাকৃতি দিতে বা কমান্ড লাইনে প্রজেক্ট করতে JMESPath; একটি জানা ফিল্ড পড়তে কোনোটিই দরকার নেই।
লক্ষ্য যদি অবজেক্টের ব্যাচকে ভিন্ন আকৃতিতে বদলানো হয়, JMESPath প্রায়ই JSONPath-এর চেয়ে সরাসরি। এটি নোড খোঁজে না; এটি একটি ফল গড়ে।
১. JMESPath কী
JMESPath JSON-এর জন্য বিশেষভাবে নকশা করা কোয়েরি ভাষা। এটি মূলত AWS CLI আউটপুট ফিল্টার করতে তৈরি হয়, পরে jmespath.js, jmespath.py ও go-jmespath-এর মতো বাস্তবায়নের মাধ্যমে অনেক ভাষায় ছড়ায়। এর লক্ষ্য JSONPath থেকে আলাদা:
- JSONPath পাথ ও শর্ত দিয়ে নোডের সেট বাছাইয়ে মনোযোগ দেয়, আর নোড সেট ফেরত দেয়;
- JMESPath ইনপুট JSON রূপান্তরে মনোযোগ দেয়, আর যেকোনো আকৃতির মান ফেরত দেয়।
এই পার্থক্যই JMESPath-কে লিস্ট প্রজেকশন, ফিল্ড পুনঃনামকরণ ও ফল ছাঁটাইয়ে বিশেষভাবে ভালো করে তোলে, যেমন API রেসপন্সের বিশটি ফিল্ড ফ্রন্টএন্ডের আসলে দরকারি তিন ফিল্ডে নামানো। JSONPath-এর মূল বিষয় এখনও না পড়লে JSONPath কোয়েরি ভাষা বাস্তবে দিয়ে শুরু করুন।
২. মূল সিনট্যাক্স: আইডেন্টিফায়ার থেকে ইনডেক্স
JMESPath JSONPath-এর চেয়ে কম চিহ্ন ব্যবহার করে আর বেশি এক্সট্রাকশন এক্সপ্রেশনের মতো পড়া যায়:
| সিনট্যাক্স | অর্থ | উদাহরণ |
|---|---|---|
name |
অবজেক্ট সদস্য অ্যাক্সেস | people |
a.b |
নেস্টেড সদস্য অ্যাক্সেস | store.bicycle.color |
[0] |
অ্যারে ইনডেক্স অ্যাক্সেস | people[0] |
[-1] |
শেষ এলিমেন্ট অ্যাক্সেস | people[-1] |
[0:2] |
স্লাইস, শুরু অন্তর্ভুক্ত ও শেষ বাদ | people[0:2] |
[*] |
লিস্ট প্রজেকশন | people[*].name |
[] |
ফ্ল্যাটেন প্রজেকশন | people[].tags[] |
[?condition] |
ফিল্টার প্রজেকশন | people[?age > 18] |
{key: expr} |
মাল্টি-সিলেক্ট হ্যাশ | {name: name, age: age} |
[expr, expr] |
মাল্টি-সিলেক্ট লিস্ট | [name, age] |
| |
বাঁ ফলের পাইপ ডানে | people[*].age | [0] |
একটি ন্যূনতম উদাহরণ:
{
"people": [
{ "name": "Alice", "age": 31, "city": "Shanghai" },
{ "name": "Bob", "age": 17, "city": "Beijing" }
]
}
people[0].name → "Alice"
people[-1].city → "Beijing"
people[0:2].name → ["Alice", "Bob"]
খেয়াল করুন people[0:2].name অ্যারে ফেরত দেয় কেন: স্লাইস লিস্ট ফেরত দেয়, আর পরের .name স্বয়ংক্রিয়ভাবে ওই লিস্টের প্রতিটি এলিমেন্টে প্রজেক্ট হয়। JMESPath ও সাধারণ অবজেক্ট অ্যাক্সেসের সবচেয়ে বড় পার্থক্য এটাই।
৩. প্রজেকশন: এক লিস্টকে আরেক লিস্টে বদলানো
প্রজেকশন JMESPath-এর মূল ধারণা। এটি দুই ধাপে কাজ করে: লিস্টের প্রতিটি এলিমেন্ট মূল্যায়ন করে, তারপর ফল নতুন লিস্টে জমা করে। যে এলিমেন্ট null-এ মূল্যায়িত হয় স্বয়ংক্রিয়ভাবে বাদ পড়ে।
people[*].name // ["Alice", "Bob"]
people[*].{name: name, age: age} // a list of objects
people[?age > 18].name // ["Alice"]
দ্বিতীয় লাইন একটি আদর্শ প্রজেকশন এক্সপ্রেশন: people[*].{name: name, age: age}। এটি প্রতিটি এলিমেন্ট { "name": ..., "age": ... } হিসেবে পুনর্গঠন করে আর বাড়তি city ফিল্ড বাদ দেয়। JSONPath-এর সঙ্গে তুলনায়, যা নোড দেয় আর আপনি কোডে map করেন, এটি একটি লুপ বাঁচায়।
দুই ধরনের প্রজেকশন আলাদা করা মূল্যবান:
[*]মূল লিস্ট কাঠামো রাখে ও null মান বাদ দেয়;[]ফ্ল্যাটেন প্রজেকশন যা আগে এক স্তর নেস্টেড লিস্ট চেপ্টায়, যাpeople[].tags[]-এর মতো "সব মানুষের সব ট্যাগ" দরকারি এক্সপ্রেশনে মানায়।
৪. পাইপ ও মাল্টি-সিলেক্ট
পাইপ | মানে "বর্তমান প্রজেকশন থামাও আর ফলকে নতুন ইনপুট ধরো"। এর সবচেয়ে সাধারণ ব্যবহার প্রজেকশনের পরে ইনডেক্স নেওয়া:
people[*].name | [0] → "Alice"
people[*].age | sort(@) | [-1] → the maximum age
পাইপ ছাড়া people[*].name[0] প্রতিটি নামের প্রথম অক্ষর নেওয়া হিসেবে ব্যাখ্যা হয়, যা আপনি চান না। পাইপ JMESPath-এর সবচেয়ে সহজে উপেক্ষিত ও সবচেয়ে উপকারী চিহ্ন।
মাল্টি-সিলেক্টের দুই রূপ:
// Multi-select hash: the result is an object
people[0].{name: name, age: age}
// Multi-select list: the result is an array
people[0].[name, age]
মাল্টি-সিলেক্ট হ্যাশ নামযুক্ত ফল গঠনের জন্য ভালো, আর মাল্টি-সিলেক্ট লিস্ট অবস্থানভিত্তিক ভোগের জন্য। দুটোই প্রজেকশনের ভেতরে নেস্ট করা যায়, কিন্তু খেয়াল করুন: মাল্টি-সিলেক্ট হ্যাশে যার মান null সেই কি তবু থাকে, অথচ প্রজেকশন পুরো null এলিমেন্ট বাদ দেয়।
৫. বিল্ট-ইন ফাংশন
JMESPath স্ট্যান্ডার্ড কিছু ফাংশন সংজ্ঞায়িত করে। সাধারণগুলো:
| ফাংশন | উদ্দেশ্য | উদাহরণ |
|---|---|---|
length() |
দৈর্ঘ্য নেওয়া | length(people) |
sort() |
মান সাজানো | sort(people[*].age) |
sort_by() |
ফিল্ড দিয়ে সাজানো | sort_by(people, &age) |
max() / min() |
সর্বোচ্চ / সর্বনিম্ন | max(people[*].age) |
sum() |
মান যোগ | sum(people[*].age) |
keys() |
অবজেক্টের কি নেওয়া | keys(people[0]) |
contains() |
অন্তর্ভুক্তি যাচাই | contains(name, "Ali") |
to_string() |
স্ট্রিংয়ে রূপান্তর | to_string(age) |
sort_by-র দ্বিতীয় আর্গুমেন্ট একটি & এক্সপ্রেশন, মানে "প্রতিটি এলিমেন্ট থেকে এই ফিল্ড নাও":
sort_by(people, &age)[*].name
এই ফাংশনগুলো JMESPath-কে সরল অ্যাগ্রিগেশন করতে দেয়, অথচ JSONPath কেবল বাছাইয়ের জন্য নকশা করা; যোগ ও গ্রুপিং সাধারণত jq বা কোড দরকার।
৬. JSONPath-এর সঙ্গে পাশাপাশি তুলনা
| মাত্রা | JSONPath | JMESPath |
|---|---|---|
| প্রাথমিক লক্ষ্য | নোড বাছাই | ডেটা রূপান্তর |
| রুট চিহ্ন | $ |
নেই; সোজা ফিল্ড-নাম লিখুন |
| চলতি নোড | @ |
@ (কেবল ফাংশন আর্গুমেন্টে) |
| ফিল্টার | [?(@.age > 18)] |
[?age > 18] |
| ওয়াইল্ডকার্ড | [*] |
[*] বা [] |
| নতুন অবজেক্ট গঠন | সমর্থিত নয় | {name: name} |
| পাইপ | সমর্থিত নয় | | |
| স্ট্যান্ডার্ড | RFC 9535 | JMESPath Specification |
এক বাক্যের সিদ্ধান্ত গাইড:
- কেবল শর্ত মেলে এমন নোড খুঁজতে হলে JSONPath ব্যবহার করুন;
- ফল ফ্রন্টএন্ডের প্রত্যাশিত আকৃতিতে বদলাতে হলে JMESPath ব্যবহার করুন;
- একটি জানা ফিল্ড পড়তে হলে কোনোটিই নয়; সোজা প্রপার্টি অ্যাক্সেস করুন।
কোয়েরির ফল ডকুমেন্টে ফেরত লিখতে হলে কোনোটিই উপযুক্ত নয়। JSON Pointer ও প্যাচ ব্যবহার করুন; দেখুন JSON Pointer, Patch ও Merge Patch।
৭. AWS CLI-তে JMESPath
AWS CLI-র --query প্যারামিটার হলো JMESPath, যা এর সবচেয়ে বিস্তৃত ব্যবহার:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id: InstanceId, State: State.Name, Type: InstanceType}' \
--output table
এই কমান্ড তিন কাজ করে: [] দিয়ে দুই নেস্টেড লিস্ট ফ্ল্যাটেন করে, মাল্টি-সিলেক্ট হ্যাশ দিয়ে কেবল তিন ফিল্ড রাখে, আর --output table দিয়ে ফল টেবিল হিসেবে রেন্ডার করে। JSONPath-এ আরও স্তর লাগত আর "পুনঃনাম প্লাস ছাঁটাই" ফল পেতেন না।
অন্যান্য সাধারণ রূপ:
# Only running instances
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,PublicIpAddress]'
# The value of the Name tag
aws ec2 describe-instances --query 'Reservations[].Instances[].Tags[?Key==`Name`].Value[]'
লক্ষ্য করুন ব্যাকটিক লিটেরাল ঘিরে, স্ট্রিং নয়। AWS CLI-তে স্ট্রিং সিঙ্গেল কোট আর JSON লিটেরাল ব্যাকটিক ব্যবহার করে। এই পার্থক্য শিক্ষানবিশদের সবচেয়ে সাধারণ ভুলের একটি।
৮. সাধারণ ভুল
[*]ও[]গুলিয়ে ফেলা: প্রথমটি নেস্টিং রাখে, দ্বিতীয়টি এক স্তর ফ্ল্যাটেন করে, আর ফল কাঠামো সম্পূর্ণ আলাদা;- পাইপ ভুলে যাওয়া:
people[*].name[0]প্রতিটি নামের প্রথম অক্ষর নেয়, প্রথম নাম নয়; - ফিল্টারের ভেতরে
@লেখা: JMESPath ফিল্টার সোজা ফিল্ড-নাম ব্যবহার করে, তাই[?@.age > 18]ভুল; null-কে খালি স্ট্রিং ধরা: প্রজেকশনnullবাদ দেয়, কিন্তু মাল্টি-সিলেক্ট হ্যাশ কি রাখে;- AWS CLI-তে লিটেরালের চারপাশে ডাবল কোট: শেল আগে সেগুলো এক্সপ্যান্ড করে এক্সপ্রেশন আবার লেখে।
ডিবাগ পরামর্শ: বাস্তব ডেটায় প্রয়োগের আগে যতটা সম্ভব ছোট ইনপুটে এক্সপ্রেশন যাচাই করুন, আর AWS CLI-তে --output json যোগ করে কাঁচা ফল দেখুন। দৈনন্দিন JSON সম্পাদনা ও ফরম্যাটিংয়ে JSON এডিটর ও কমান্ড-লাইন টুল-এর সঙ্গে জোড়া দিলে ফিল্ড পাথ খোঁজা অনেক দ্রুত হয়।
সাধারণ প্রশ্ন
JMESPath ও JSONPath একসঙ্গে ব্যবহার করা যায়?
হ্যাঁ, তবে সাধারণত দরকার নেই। প্রচলিত পথ হলো JMESPath দিয়ে ছোট অবজেক্ট ছাঁটা, তারপর কোডে সাধারণ প্রপার্টি অ্যাক্সেস। সত্যিই পাথ দিয়ে লোকেট করে বদলাতে হলে JSONPath পড়ে আর JSON Pointer লেখে, যা আরও স্পষ্ট শ্রমবিভাজন।
people[*].name একটি একক স্ট্রিং ছাড়া অন্য কিছু ফেরত দেয় কেন?
কারণ [*] একটি প্রজেকশন, আর ফল সবসময় লিস্ট, এমনকি একজন মানুষ থাকলেও। একক মান পেতে পাইপ ও ইনডেক্স যোগ করুন: people[*].name | [0]।
প্রজেকশনের ভেতরের null-এর কী হয়?
লিস্ট প্রজেকশন স্বয়ংক্রিয়ভাবে null-এ মূল্যায়িত এলিমেন্ট বাদ দেয়, তাই ফল মূল লিস্টের চেয়ে ছোট হতে পারে। মাল্টি-সিলেক্ট হ্যাশ কি রাখে ও null মান সংরক্ষণ করে, যা API রেসপন্স গঠনে গুরুত্বপূর্ণ।
JMESPath কি অ্যাগ্রিগেশন ও গ্রুপিং করতে পারে?
sum(), max() ও sort_by() দিয়ে সরল অ্যাগ্রিগেশন করতে পারে। গ্রুপিং, join ও window ফাংশন স্ট্যান্ডার্ডের বাইরে। জটিল বিশ্লেষণ jq, SQL বা কোডে ছাড়ুন।
JMESPath ও JSONPath-এর মধ্যে কীভাবে ঠিক করব?
পাথ ও শর্ত দিয়ে নোড বাছাই করে নোড সেট পেতে হলে JSONPath; ফল ফ্রন্টএন্ডের প্রত্যাশিত আকৃতিতে পুনরাকৃতি দিতে, ফিল্ড পুনঃনাম ও ছাঁটাইসহ, JMESPath। একটি জানা ফিল্ড পড়তে হলে কোনোটিই নয়; সোজা প্রপার্টি অ্যাক্সেস করুন।
প্রথম প্রজেক্ট করা মান নিতে পাইপ ব্যবহার করা উচিত?
হ্যাঁ। people[*].name প্রজেকশন, তাই ফল সবসময় লিস্ট; পাইপ ছাড়া people[*].name[0] প্রতিটি নামের প্রথম অক্ষর নেয়। প্রথম মান পেতে people[*].name | [0] লিখুন।