JMESPath برای مبتدیان: پرسوجوهای projection که از JSONPath بهترند
جمعبندی: JMESPath دستههای اشیا را مستقیمتر از JSONPath بازشکل میدهد. این راهنمای مبتدی نحو، projectionها، pipeها، توابع داخلی و استفاده در AWS CLI را پوشش میدهد.

جمعبندی: از JSON Pointer برای مکانیابی بر اساس موقعیت، JSONPath برای فیلتر یا بازگشت، و JMESPath وقتی باید نتیجه را بازشکل دهید یا روی خط فرمان projection کنید استفاده کنید؛ یک فیلد شناختهشده به هیچکدام نیاز ندارد.
اگر هدف بازشکل دادن دستهای شیء به شکلی دیگر است، JMESPath غالباً مستقیمتر از JSONPath است. گره نمیجوید؛ نتیجه میسازد.
۱. JMESPath چیست
JMESPath زبانی است که بهطور اختصاصی برای JSON طراحی شده. ابتدا برای فیلتر خروجی AWS CLI ساخته شد و بعد از طریق پیادهسازیهایی مانند jmespath.js، jmespath.py و go-jmespath به زبانهای بسیاری سرایت کرد. هدفش با JSONPath متفاوت است:
- JSONPath بر انتخاب مجموعهای گره با مسیر و شرط تمرکز دارد و مجموعه گره برمیگرداند؛
- JMESPath بر تبدیل JSON ورودی تمرکز دارد و مقداری با هر شکلی برمیگرداند.
این تفاوت JMESPath را مخصوصاً در projection فهرست، تغییر نام فیلد و کوتاهسازی نتیجه خوب میکند، مثلاً کاهش بیست فیلد پاسخ API به سه فیلدی که فرانتاند واقعاً لازم دارد. اگر مبانی JSONPath را نخواندهاید، از زبان پرسوجوی JSONPath در عمل شروع کنید.
۲. نحو پایه: از شناسه تا اندیس
JMESPath نمادهای کمتری از JSONPath دارد و بیشتر شبیه عبارت استخراج خوانده میشود:
| نحو | معنا | مثال |
|---|---|---|
name |
دسترسی به عضو شیء | people |
a.b |
دسترسی به اعضای تودرتو | store.bicycle.color |
[0] |
دسترسی به اندیس آرایه | people[0] |
[-1] |
دسترسی به آخرین عنصر | people[-1] |
[0:2] |
برش، شروع شامل و پایان انحصاری | people[0:2] |
[*] |
projection فهرست | people[*].name |
[] |
projection تختساز | people[].tags[] |
[?condition] |
projection فیلتر | 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 بعدی خودکار روی هر عنصر آن فهرست projection میشود. این بزرگترین تفاوت JMESPath با دسترسی معمول شیء است.
۳. Projectionها: تبدیل یک فهرست به فهرستی دیگر
projection مفهوم اصلی در JMESPath است. دو گام دارد: هر عنصر فهرست را ارزیابی کن، سپس نتایج را در فهرست جدید جمع کن. عناصری که به null ارزیابی میشوند خودکار حذف میشوند.
people[*].name // ["Alice", "Bob"]
people[*].{name: name, age: age} // a list of objects
people[?age > 18].name // ["Alice"]
خط دوم عبارت projection معمولی است: people[*].{name: name, age: age}. هر عنصر را بهصورت { "name": ..., "age": ... } بازمیسازد و فیلد اضافه city را دور میریزد. در مقایسه با JSONPath که گرهها را میدهد و بعد در کد map میکنید، این یک حلقه صرفهجویی میکند.
دو نوع projection شایسته تشخیصاند:
[*]ساختار فهرست اصلی را حفظ و مقادیر null را رد میکند؛[]projection تختساز است که اول یک سطح فهرست تودرتو را جمع میکند، مناسب عبارتهایی مانندpeople[].tags[]که «همه برچسبهای همه افراد» میخواهند.
۴. Pipeها و چندانتخابی
pipe یعنی «projection جاری را متوقف کن و نتیجه را ورودی جدید بدان». رایجترین استفادهاش گرفتن اندیس پس از یک projection است:
people[*].name | [0] → "Alice"
people[*].age | sort(@) | [-1] → the maximum age
بدون pipe، people[*].name[0] بهعنوان گرفتن نخستین نویسه هر نام تفسیر میشود، که منظور شما نیست. pipe نادیدهگرفتهشدهترین و مفیدترین نماد 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]
هشهای چندانتخابی برای ساخت نتایج نامدار خوباند، در حالی که فهرستهای چندانتخابی برای مصرف موقعیتی خوباند. هر دو میتوانند در projectionها تودرتو شوند، اما توجه کنید: در هش چندانتخابی، کلیدی که مقدارش null است همچنان نگه داشته میشود، در حالی که projection کل عنصر 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} |
| Pipeها | پشتیبانی نمیشود | | |
| استاندارد | RFC 9535 | مشخصات JMESPath |
راهنمای تصمیم یکجملهای:
- وقتی فقط باید گرههای مطابق شرط را پیدا کنید از JSONPath استفاده کنید؛
- وقتی باید نتیجه را به آنچه فرانتاند انتظار دارد بازشکل دهید از JMESPath؛
- وقتی یک فیلد شناختهشده میخوانید از هیچکدام؛ مستقیم به ویژگی دسترسی دهید.
اگر نتیجه پرسوجو باید در سند نوشته شود، هیچیک از دو زبان مناسب نیست. بهجایش از JSON Pointer و patchها استفاده کنید؛ JSON Pointer، Patch و Merge Patch را ببینید.
۷. JMESPath در AWS CLI
پارامتر --query در AWS CLI همان 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[]'
توجه کنید backtickها literal را در بر میگیرند، نه رشته را. در AWS CLI، رشتهها از تکگیومه و literalهای JSON از backtick استفاده میکنند. این تفاوت یکی از رایجترین اشتباهات مبتدیان است.
۸. اشتباهات رایج
- قاطی کردن
[*]و[]: اولی تودرتویی را حفظ میکند، دومی یک سطح تخت میکند و ساختارهای حاصل کاملاً متفاوتاند؛ - فراموش کردن pipe:
people[*].name[0]نخستین نویسه هر نام را میگیرد، نه نخستین نام را؛ - نوشتن
@داخل فیلتر: فیلترهای JMESPath نام فیلد را مستقیم استفاده میکنند، پس[?@.age > 18]اشتباه است؛ - برخورد با
nullبهعنوان رشته خالی: projectionهاnullرا میاندازند، اما هشهای چندانتخابی کلید را نگه میدارند؛ - استفاده از گیومه دوتایی حول literal در AWS CLI: شل اول بازشان میکند و عبارت را بازنویسی میکند.
توصیه عیبیابی: عبارت را روی کوچکترین ورودی ممکن اعتبارسنجی کنید پیش از اعمال به داده واقعی، و در AWS CLI --output json اضافه کنید تا نتایج خام را ببینید. برای ویرایش و قالببندی روزمره JSON، همراه کردن با ویرایشگرها و ابزارهای خط فرمان JSON مکانیابی مسیر فیلد را بسیار سریعتر میکند.
پرسشهای متداول
آیا JMESPath و JSONPath میتوانند با هم استفاده شوند؟
بله، اما معمولاً غیرضروری است. رویکرد رایج کوتاه کردن شیء کوچکی با JMESPath و سپس دسترسی معمول به ویژگی در کد است. اگر واقعاً باید با مسیر مکانیابی و سپس تغییر دهید، JSONPath میخواند و JSON Pointer مینویسد، که تقسیم کار روشنتری است.
چرا people[*].name چیزی جز یک رشته برمیگرداند؟
چون [*] یک projection است و نتیجه همیشه فهرست است، حتی وقتی فقط یک نفر وجود دارد. برای گرفتن یک مقدار، pipe و اندیس اضافه کنید: people[*].name | [0].
با null داخل projection چه میشود؟
projection فهرست عناصری که به null ارزیابی میشوند را خودکار رد میکند، پس نتیجه ممکن است کوتاهتر از فهرست اصلی باشد. هش چندانتخابی کلید را نگه میدارد و مقدار null را حفظ میکند، که هنگام ساخت پاسخ API مهم است.
آیا JMESPath میتواند تجمیع و گروهبندی انجام دهد؟
تجمیع پایه با sum()، max() و sort_by() میتواند. گروهبندی، join و توابع پنجره بیرون از استانداردند. تحلیل پیچیده را به jq، SQL یا کد بسپارید.
چگونه بین JMESPath و JSONPath تصمیم بگیرم؟
وقتی باید گرهها را با مسیر و شرط انتخاب کنید و مجموعه گره بگیرید از JSONPath استفاده کنید؛ وقتی باید نتیجه را با تغییر نام و کوتاهسازی فیلد به آنچه فرانتاند انتظار دارد بازشکل دهید از JMESPath. وقتی یک فیلد شناختهشده میخوانید از هیچکدام؛ مستقیم به ویژگی دسترسی دهید.
آیا برای گرفتن نخستین مقدار projection باید از pipe استفاده کنم؟
بله. people[*].name یک projection است، پس نتیجه همیشه فهرست است؛ بدون pipe، people[*].name[0] نخستین نویسه هر نام را میگیرد. people[*].name | [0] بنویسید تا نخستین مقدار را بگیرید.