JSONPath زبانی برای انتخاب، پیمایش و استخراج بخشهای مشخص از دادههای JSON است. وقتی یک پاسخ API بزرگ، فایل تنظیمات یا سند تودرتو دارید، لازم نیست همه ساختار را دستی باز کنید؛ با یک عبارت JSONPath میتوانید دقیقاً به فیلد، عضو Array یا مجموعهای از مقادیر برسید. قدرت JSONPath در این است که مسیرهای ساده، Wildcard، فیلتر، Recursive Descent و انتخاب چند نتیجه را در قالبی فشرده ارائه میکند. بااینحال پیادهسازیهای مختلف همیشه رفتار کاملاً یکسانی ندارند و برای استفاده مطمئن باید Syntax ابزار یا کتابخانه هدف را بررسی و عبارتها را با داده واقعی آزمایش کنید.
پاسخ سریع: JSONPath چیست؟
JSONPath یک زبان Query برای JSON است که با نمادهایی مانند $ برای ریشه، نقطه برای دسترسی به Property، براکت برای کلید یا Index، ستاره برای Wildcard و Filter برای انتخاب شرطی کار میکند. برای مثال عبارت $.store.books[0].title عنوان اولین کتاب را از یک Object تودرتو استخراج میکند. JSONPath داده را اعتبارسنجی یا تغییر نمیدهد؛ وظیفه اصلی آن انتخاب و استخراج Nodeهای موجود است.
چرا JSONPath اهمیت دارد؟
دادههای JSON در APIها اغلب چندین سطح Object و Array دارند. دسترسی دستی به چنین ساختاری هم خطاپذیر است و هم در ابزارهای تست، مانیتورینگ و تبدیل داده مقیاسپذیر نیست. JSONPath اجازه میدهد یک مسیر قابل تکرار تعریف کنید و همان Query را روی چند پاسخ اجرا کنید.
- استخراج یک مقدار مشخص از پاسخ API
- انتخاب تمام اعضای یک Array
- پیدا کردن Objectهایی که شرط خاصی دارند
- بررسی وجود یا مقدار فیلد در تست خودکار
- آمادهسازی داده برای تبدیل، گزارشگیری یا پردازش بعدی
- جستجو در ساختارهای عمیق بدون نوشتن حلقههای متعدد
ساختار نمونه برای آموزش JSONPath
در مثالهای این مقاله از داده زیر استفاده میکنیم:
{
"store": {
"books": [
{
"id": 1,
"title": "Clean Code",
"price": 25,
"available": true,
"tags": ["programming", "quality"]
},
{
"id": 2,
"title": "Design Patterns",
"price": 40,
"available": false,
"tags": ["architecture", "oop"]
},
{
"id": 3,
"title": "Refactoring",
"price": 32,
"available": true,
"tags": ["programming", "design"]
}
],
"location": {
"city": "Berlin",
"country": "DE"
}
}
}
نماد ریشه در JSONPath
بیشتر پیادهسازیها از $ برای اشاره به Root Document استفاده میکنند. هر Query از ریشه آغاز میشود و سپس با Property یا Index ادامه پیدا میکند.
$
این عبارت کل سند را انتخاب میکند. اگر هدف انتخاب Object فروشگاه باشد، مسیر به شکل زیر است:
$.store
دسترسی نقطهای به Propertyها
رایجترین Syntax استفاده از نقطه است. هر بخش پس از نقطه نام یک Property را نشان میدهد:
$.store.location.city
خروجی این مسیر مقدار Berlin است. این روش برای کلیدهای ساده و بدون فاصله مناسب است.
چه زمانی Dot Notation کافی نیست؟
اگر کلید شامل فاصله، خط تیره، نقطه یا نویسهای باشد که Parser آن را بخشی از Syntax میداند، از Bracket Notation استفاده کنید. برای نمونه، برای کلید display-name معمولاً عبارت زیر مطمئنتر است:
$['display-name']
Bracket Notation برای کلیدها و Indexها
براکت هم برای نام Property و هم برای Index Array استفاده میشود:
$['store']['books'][0]['title']
این Query همان نتیجه $.store.books[0].title را میدهد. Bracket Notation در تولید خودکار Query یا کار با کلیدهای خاص مفیدتر است.
انتخاب اعضای Array با Index
Index آرایه معمولاً از صفر آغاز میشود. بنابراین [0] اولین عضو و [1] دومین عضو است:
$.store.books[1].title
نتیجه این مسیر عنوان کتاب دوم است. اگر Index خارج از محدوده باشد، بعضی کتابخانهها نتیجه خالی میدهند و بعضی رفتار متفاوتی دارند؛ این مورد را در Runtime هدف تست کنید.
Wildcard در JSONPath
ستاره * برای انتخاب همه اعضا یا همه Propertyها به کار میرود. برای دریافت عنوان همه کتابها:
$.store.books[*].title
خروجی منطقی مجموعهای شامل سه عنوان است. Wildcard روی Object نیز میتواند همه مقدارهای Propertyهای سطح موردنظر را انتخاب کند، اما شکل خروجی به کتابخانه بستگی دارد.
| عبارت | معنا | نوع نتیجه معمول |
|---|---|---|
$ | کل سند | Object یا Array ریشه |
$.store | Property با نام store | یک Node |
$.store.books[0] | اولین عضو books | یک Object |
$.store.books[*] | همه اعضای books | مجموعهای از Nodeها |
$.store.books[*].title | عنوان همه کتابها | مجموعه String |
Recursive Descent چیست؟
Recursive Descent معمولاً با .. نوشته میشود و در تمام عمقهای بعدی دنبال یک نام میگردد. برای یافتن همه Propertyهای title در هر سطح:
$..title
این Syntax وقتی مفید است که محل دقیق فیلد ثابت نیست؛ اما روی سند بزرگ میتواند پرهزینه و بیشازحد گسترده باشد. اگر ساختار را میدانید، مسیر دقیق معمولاً خواناتر و سریعتر است.
Filter Expression در JSONPath
فیلتر برای انتخاب اعضایی از Array است که شرط مشخصی دارند. نماد @ معمولاً به عضو جاری اشاره میکند. برای انتخاب کتابهای موجود:
$.store.books[?(@.available == true)]
برای انتخاب عنوان کتابهایی با قیمت بیشتر از 30:
$.store.books[?(@.price > 30)].title
در HTML، علامت بزرگتر در کد بهصورت Escape شده ذخیره شده است؛ ابزار JSONPath خود عبارت واقعی را با علامت > دریافت میکند.
ترکیب چند شرط
برخی پیادهسازیها از عملگرهای منطقی برای ترکیب شرطها پشتیبانی میکنند:
$.store.books[?(@.available == true && @.price < 35)]
پشتیبانی از عملگرها و قواعد تبدیل نوع میان کتابخانهها متفاوت است. در یک Runtime ممکن است مقایسه String و Number خطا باشد و در دیگری تبدیل ضمنی رخ دهد. روی رفتار ضمنی تکیه نکنید.
انتخاب چند Index یا Slice
بعضی پیادهسازیها Union یا Slice را پشتیبانی میکنند. برای نمونه، انتخاب عضو اول و سوم ممکن است چنین نوشته شود:
$.store.books[0,2]
Slice نیز معمولاً الگویی شبیه [start:end:step] دارد:
$.store.books[0:2]
این بخش از JSONPath بیش از مسیرهای ساده در پیادهسازیها تفاوت دارد. پیش از استفاده در Production، Syntax دقیق کتابخانه را بررسی کنید.
تفاوت JSONPath با دسترسی معمول در JavaScript
در JavaScript میتوانید مستقیم بنویسید data.store.books[0].title. این روش برای مسیر ثابت و منطق داخل برنامه مناسب است. JSONPath زمانی ارزش بیشتری دارد که Query بهصورت داده ذخیره شود، کاربر آن را وارد کند، مسیر پویا باشد یا یک ابزار عمومی روی JSONهای مختلف کار کند.
| معیار | دسترسی مستقیم | JSONPath |
|---|---|---|
| مسیر ثابت در کد | ساده و سریع | ممکن است اضافه باشد |
| Query قابل تنظیم | نیازمند منطق سفارشی | مناسبتر |
| فیلتر مجموعه | با filter و map | در یک عبارت |
| کنترل نوع و خطا | در اختیار برنامهنویس | وابسته به Engine |
| قابلیت حمل | وابسته به زبان | نسبی؛ Dialectها متفاوتاند |
تفاوت JSONPath با JSON Pointer
JSON Pointer برای اشاره دقیق به یک Location در سند طراحی شده و Syntax ساده و قطعیتری دارد. JSONPath قابلیت Query، Wildcard، Filter و بازگرداندن چند نتیجه را ارائه میکند. بنابراین Pointer بیشتر برای آدرسدهی دقیق و JSONPath برای جستجو و استخراج مناسب است.
JSONPath چه کاری انجام نمیدهد؟
- JSON را از نظر Syntax معتبر نمیکند.
- ساختار داده را با JSON Schema مقایسه نمیکند.
- بهطور ذاتی داده را رمزنگاری یا امن نمیکند.
- در همه ابزارها قابلیت Update یا Delete ندارد.
- تضمین نمیکند Query در تمام کتابخانهها نتیجه یکسان دهد.
اگر داده شما Parse نمیشود، ابتدا از JSON Validator استفاده کنید. اگر هدف کنترل نوع و قواعد ساختاری است، JSON Schema Validator ابزار درستتری است.
مثالهای کاربردی JSONPath
استخراج شناسه همه رکوردها
$.store.books[*].id
استخراج Objectهای دارای موجودی
$.store.books[?(@.available == true)]
پیدا کردن همه قیمتها در هر عمق
$..price
انتخاب Tagهای تمام کتابها
$.store.books[*].tags[*]
انتخاب کتاب با شناسه مشخص
$.store.books[?(@.id == 2)]
کار با نتیجه تکمقداری و چندمقداری
یک اشتباه رایج این است که همیشه انتظار یک Scalar داشته باشیم. Queryهایی مانند $.store.location.city معمولاً یک نتیجه دارند، اما Wildcard، Filter و Recursive Descent میتوانند چند Node بازگردانند. API کتابخانه ممکن است همیشه Array بدهد یا برای نتیجه واحد یک مقدار مستقیم برگرداند. قرارداد نتیجه را در کد مدیریت کنید.
اگر Property وجود نداشته باشد چه میشود؟
در بسیاری از Engineها مسیر ناموجود نتیجه خالی تولید میکند، نه خطای Syntax. این تفاوت مهم است: Query ممکن است کاملاً معتبر باشد اما در داده فعلی Match نداشته باشد. برای تست API، نبودن Match را جدا از خراب بودن Query گزارش کنید.
کلیدهایی با نقطه، فاصله یا نویسه خاص
فرض کنید داده چنین کلیدی دارد:
{
"user.profile": {
"display name": "Mina"
}
}
Dot Notation این کلیدها را اشتباه تفسیر میکند. Bracket Notation انتخاب مطمئنتری است:
$['user.profile']['display name']
JSONPath در JavaScript
کتابخانههای مختلف JavaScript API متفاوتی دارند، اما الگوی عمومی چنین است:
const result = queryJsonPath({
path: '$.store.books[*].title',
json: data
});
console.log(result);
queryJsonPath در این مثال نام عمومی و فرضی است؛ نام تابع واقعی را از کتابخانه انتخابشده بگیرید. ورودی Query را پیش از اجرا محدود کنید، مخصوصاً اگر توسط کاربر تولید میشود.
JSONPath در Python
در Python نیز کتابخانهها Syntax و API یکسانی ندارند. الگوی مفهومی:
expression = compile_jsonpath('$.store.books[*].title')
matches = expression.find(data)
values = [match.value for match in matches]
compile_jsonpath نیز نام نمادین است. هدف مثال نشاندادن مراحل Compile، Find و استخراج Value است، نه معرفی API یک Package مشخص.
JSONPath در تست API
در تست خودکار میتوان از JSONPath برای انتخاب مقدار و سپس Assert استفاده کرد. برای نمونه:
Query: $.store.books[?(@.id == 1)].available
Expected: true
تست خوب باید سه حالت را جدا کند: Query نامعتبر، Query معتبر بدون Match و Query معتبر با مقدار غیرمنتظره. ادغام این سه حالت، عیبیابی را دشوار میکند.
خطاهای رایج JSONPath
فراموش کردن Root
بعضی Engineها مسیر بدون $ را قبول میکنند و بعضی نه. برای قابلیت حمل بهتر از Syntax مستند ابزار استفاده کنید.
استفاده از Index اشتباه
Arrayها معمولاً Zero-based هستند. [1] عضو دوم است، نه اول.
استفاده از Dot Notation برای کلید خاص
کلید دارای نقطه یا فاصله باید با Bracket Notation انتخاب شود.
انتظار یک مقدار از Query چندنتیجهای
Wildcard و Filter اغلب Collection برمیگردانند. نوع نتیجه را صریح مدیریت کنید.
مقایسه نوعهای ناسازگار
مقایسه "25" با Number ممکن است در Engineهای مختلف رفتار متفاوتی داشته باشد. داده و Query را Type-safe طراحی کنید.
استفاده بیرویه از Recursive Descent
$..* روی سند بزرگ میتواند خروجی بسیار زیاد و مصرف پردازشی بالا ایجاد کند.
فرض سازگاری کامل بین کتابخانهها
Filter، Regex، Functionها، Slice و ترتیب نتایج ممکن است متفاوت باشند. تست سازگاری بخشی از مهاجرت کتابخانه است.
Troubleshooting مرحلهبهمرحله
- ابتدا مطمئن شوید ورودی JSON از نظر Syntax معتبر است.
- Query را با
$یا مسیر ریشه مورد انتظار شروع کنید. - مسیر را مرحلهای کوتاه کنید؛ ابتدا
$.storeو سپس بخشهای بعدی را اضافه کنید. - نوع Node را در هر مرحله بررسی کنید؛ Object یا Array بودن مهم است.
- برای کلیدهای خاص از Bracket Notation استفاده کنید.
- Filter را ابتدا با یک شرط ساده آزمایش کنید.
- نوع مقدار مقایسهشده را کنترل کنید.
- اگر نتیجه خالی است، تفاوت Case و نام دقیق Property را بررسی کنید.
- Query را در همان Engine مورد استفاده Production تست کنید.
نکات امنیتی JSONPath
خود JSONPath رمزنگاری یا دسترسی امن ایجاد نمیکند. اگر کاربر Query دلخواه وارد میکند، محدودیت طول، عمق، تعداد نتایج و زمان اجرا تعیین کنید. Queryهای گسترده میتوانند مصرف CPU و حافظه را افزایش دهند. اگر Engine از Expressionهای اسکریپتی یا Function سفارشی پشتیبانی میکند، آن قابلیتها را برای ورودی غیرقابل اعتماد محدود کنید.
- برای Query طولانی سقف تعیین کنید.
- Recursive Descent و Wildcard گسترده را محدود کنید.
- حداکثر تعداد Match را مشخص کنید.
- Timeout یا Cancellation داشته باشید.
- پیام خطا را بدون افشای داده حساس برگردانید.
- Query و داده حساس را در Log عمومی ثبت نکنید.
نکات عملکرد
مسیر دقیق معمولاً از جستجوی بازگشتی سریعتر است. اگر یک Query بارها روی دادههای مختلف اجرا میشود و کتابخانه Compile را پشتیبانی میکند، Expression را Cache کنید. از بازگرداندن کل Object زمانی که فقط یک فیلد لازم است خودداری کنید. روی فایلهای بسیار بزرگ، Parser کامل در حافظه ممکن است خود محدودیت اصلی باشد؛ JSONPath لزوماً Streaming نیست.
Best Practices برای نوشتن Query
- کوتاهترین مسیر دقیق و خوانا را انتخاب کنید.
- برای کلیدهای پیچیده از Bracket Notation استفاده کنید.
- نوع نتیجه مورد انتظار را مستند کنید.
- Query را کنار نمونه داده تست کنید.
- Dialect یا کتابخانه مورد استفاده را در پروژه ثابت نگه دارید.
- برای Filterهای پیچیده تست مرزی بنویسید.
- بین «بدون Match» و «Query نامعتبر» تفاوت بگذارید.
- Queryهای کاربر را از نظر هزینه اجرا محدود کنید.
- در مهاجرت کتابخانه، مجموعه تست سازگاری اجرا کنید.
چکلیست JSONPath
- آیا JSON ورودی معتبر است؟
- آیا مسیر از Root درست شروع میشود؟
- آیا در هر مرحله نوع Object و Array را درست در نظر گرفتهاید؟
- آیا Index آرایه صحیح است؟
- آیا کلید خاص با Bracket Notation نوشته شده است؟
- آیا Query یک نتیجه یا چند نتیجه برمیگرداند؟
- آیا Filter نوع داده را درست مقایسه میکند؟
- آیا Recursive Descent واقعاً لازم است؟
- آیا Query در Engine نهایی تست شده است؟
- آیا محدودیت زمان، اندازه و تعداد Match دارید؟
جمعبندی
JSONPath راهی فشرده برای پیمایش و استخراج داده از JSON است. با Root، Dot Notation، Bracket Notation، Index، Wildcard، Recursive Descent و Filter میتوان از انتخاب یک مقدار ساده تا جستجوی شرطی در Arrayهای تودرتو را انجام داد. نکته کلیدی این است که JSONPath استاندارد اجرایی کاملاً یکدستی در همه کتابخانهها ندارد؛ بنابراین Query باید در همان Engine هدف آزمایش شود. مسیرهای دقیق، مدیریت صریح نتیجههای چندگانه، کنترل نوع در Filter و محدودکردن Queryهای پرهزینه، استفاده از JSONPath را قابل اعتمادتر میکند.