JSON Toolkit

שפת השאילתות JSONPath בפועל

שורה תחתונה: ב-2024 קיבל JSONPath סוף סוף תקן רשמי, RFC 9535. המאמר מסביר את התחביר, סמנטיקת מסננים, פונקציות ואת ההבדלים ההיסטוריים בין מימושים.

בינוניכ-12 דקות קריאהפורסם 2026-10-30עודכן 2026-10-30
עטיפה עבור שפת השאילתות JSONPath בפועל

שורה תחתונה: אתרו קודם עם נתיב מפורש וסננו על גביו, וכתבו ביטויים לפי RFC 9535; השתמשו בגישה לתכונות או ב-JSON Pointer עבור שדה קבוע יחיד, וב-jq או בקוד עבור אגרגציה.

בטח ראיתם ביטויים כמו $.store.book[?(@.price < 10)].title, אבל הוא הפך לתקן בינלאומי רק ב-2024.

1. מתקן דה-פקטו לתקן רשמי

JSONPath הוצע על ידי Stefan Gössner ב-2007, ובמשך זמן רב ה"מפרט" היחיד היה פוסט בלוג אחד, כשכל מימוש שפה עשה מה שנראה לו:

  • האם מסננים צריכים סוגריים (?(@.a) מול ?@.a);
  • האם אינדקס המערך [-1] נתמך;
  • האם התוצאה מחזירה "ערכים" או "ערכים ונתיבים";
  • האם ביטויי סקריפט (?(@.price < $.max)) מותרים.

RFC 9535, שפורסם בפברואר 2024, איחד את הפערים הללו לתחביר וסמנטיקה אחת וסיפק חבילת מבחני תאימות רשמית. פרויקטים חדשים צריכים ללכת לפי RFC 9535; בהגירת פרויקטים ישנים, אשרו קודם את גרסת המימוש.

2. דף עזר לתחביר

תחביר משמעות דוגמה
$ צומת השורש $.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 מחזיר קבוצת צמתים (nodelist), לא ערך יחיד. גם כשרק צומת אחד מתאים, התוצאה היא רשימה באורך אחד.

3. מעבר על התחביר עם דוגמה אחת

{
  "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 4 מחירים (כולל מחיר האופניים)
$.store.book[*].title 3 כותרות
$.store.book[?@.price < 10].title כותרות הספרים 1 ו-3
$.store.book[?@.tags] כל הספרים שיש להם tags (בדיקת קיום)
$.store.book[-1].title הכותרת של הספר האחרון
$..book[0,1] שני הספרים הראשונים
$.store.book[?@.price < $.expensive] תלוי מימוש מבחינה תחבירית; RFC 9535 אינו מתיר הפניות לשורש

השורה האחרונה היא מלכודת היסטורית: מימושים רבים מתירים $.expensive בתוך מסנן להשוואה בין צמתים, מה ש-RFC 9535 אוסר במפורש — מסנן יכול לגשת רק לצומת הנוכחי @. כשצריך להשוות לסף גלובלי, העבירו את הסף כארגומנט לפונקציית השאילתה.

4. מה מסננים יכולים לבטא

ביטויי מסנן של 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), שאינו תומך לא ב-backreferences ולא ב-lookaround — זה גם מגבלה וגם מגן נוח מפני regex DoS.

5. הבדלי מימושים: בדקו לפני הגירה

מימוש שפה תמיכה ב-RFC 9535 הערות
jsonpath-plus JavaScript תואם ברובו תומך בפונקציות הרחבה ובביטויי סקריפט; יכולת גבוהה יותר אבל לא תקני לחלוטין
jsonpath (dchester) JavaScript חלקי ספרייה ותיקה עם תחביר ?() ומבנה תוצאה שונה
ojg / gjson Go ל-ojg יש תמיכה טובה gjson משתמש בתחביר נתיב אחר ואינו JSONPath
jsonpath-ng Python חלקי תומך בהרחבות ומתירן למדי
Jayway JsonPath Java חלקי אקוסיסטם רחב; התנהגות היסטורית שונה מה-RFC

שלוש בדיקות לפני הגירה:

  1. האם סוגריים במסנן חובה;
  2. האם אינדקסים שליליים כמו -1 נתמכים;
  3. האם התוצאה היא "מערך ערכים" או "מערך אובייקטים {path, value}".

השלישית היא הקלה ביותר להיתקל בה: הפרונט קורא result[0], ואחרי החלפת ספרייה הוא מקבל אובייקט, והעמוד מתרוקן.

6. ביצועים ואבטחה

ביצועים

  • JSONPath הוא בעצם מעבר אחד עם מורכבות O(מספר צמתים), והוא לא יכול להשתמש באינדקסים;
  • ירידה רקורסיבית עם .. היא היקרה ביותר; אם אפשר לאתר עם $.store.book קודם ואז לסנן, אל תכתבו $..book;
  • קמפלו ביטויים פעם אחת והשתמשו בהם שוב (רוב הספריות מציעות compile או מטמון) במקום לפרסר בכל שאילתה;
  • במסמכים עם יותר מ-100,000 צמתים, שקלו חילוץ מדויק עם JSON Pointer קודם, או עיבוד מקדים בשרת עם jq.

אבטחה

  • לביטויים שמגיעים מקלט משתמש חייבים להיות גבולות אורך ועומק קינון;
  • אל תתנו ל-JSONPath לנהל הרשאות — הוא יכול רק לקרוא, ולא יכול לבטא "אילו שדות אינם נראים";
  • כשהביטוי הרגולרי שמשמש את match() מגיע ממשתמשים, הגבילו את המורכבות שלו; למרות שהוא בטוח יותר מ-PCRE, עדיין מומלץ גבול אורך.

7. מתי לא להשתמש ב-JSONPath

  • צריך רק שדה קבוע אחד → השתמשו ישירות ב-data.store.bicycle.color;
  • צריך לכתוב בחזרה אחרי שינוי → השתמשו ב-JSON Pointer או בפאצ'; JSONPath לא כותב בחזרה;
  • צריך אגרגציה (סכימה, קיבוץ) → השתמשו ב-jq, ב-SQL או בקוד; JSONPath רק בוחר;
  • המסמך ענק וצריך רק כמה שדות → השתמשו בפרסר זורם במקום לקרוא את כל העץ לזיכרון קודם.

8. רשימת בדיקה

  • [ ] הביטויים עומדים ב-RFC 9535 ואינם מסתמכים על הפניות לשורש;
  • [ ] ברור שהתוצאה היא קבוצת צמתים; משתמשים ב-[0] או ב-value() כדי לקבל ערך יחיד;
  • [ ] ביטויים מקומפלים מראש ונעשה בהם שימוש חוזר;
  • [ ] לביטויים בשליטת משתמש יש מגבלות אורך ומורכבות;
  • [ ] מקרי מבחן התאימות הרשמיים מורצים בעת החלפת מימוש.

שאלות נפוצות

איך בוחרים בין JSON Pointer ל-JSONPath?

השתמשו בגישה רגילה לתכונה או ב-JSON Pointer כשצריך שדה קבוע אחד; פנו ל-JSONPath כשצריך מסננים, תווים כלליים או ירידה רקורסיבית. JSONPath רק בוחר ולעולם לא כותב בחזרה, ולכן כתיבה בחזרה שייכת ל-JSON Pointer או לפאצ'.

למה מסנן לא יכול להפנות ל-$.expensive?

RFC 9535 אוסר במפורש הפניות לשורש; מסנן רשאי לגשת רק לצומת הנוכחי @. העבירו את הסף כארגומנט לפונקציית השאילתה במקום. מימושים ישנים מקבלים את התחביר, אבל הוא נשבר כשמחליפים או משדרגים ספרייה.

כדאי להסתמך על result[0] כדי לקרוא ערך?

רק אחרי שאישרתם שהמימוש הנוכחי מחזיר מערך ערכים. חלק מהספריות מחזירות אובייקטים {path, value}, ואז result[0] הופך לאובייקט והעמוד מתרוקן. קראו ערכים בודדים דרך [0] או value(), והריצו את חבילת התאימות הרשמית לפני החלפת מימושים.

מקורות

כלים מתאימים

מאמרים קשורים

JSON Pointer, Patch ו-Merge Patch

שלושה מפרטים קצרים יוצרים פרוטוקול שינוי: Pointer מאתר, Patch עורך בדיוק, Merge Patch ממזג חלקית. בחירה שגויה כואבת במערכים ובמקביליות.

בינוניכ-12 דקות קריאהעודכן 2026-10-30

השוואת ספריות לפרסור ולעיבוד JSON

ממשקי API מקוריים הם המהירים ביותר, מפרסרים סובלניים מספקים היסטים. בחרו לפי שלושה דברים: איתור השגיאה, האם הטקסט המקורי נשמר, והאם הגבולות מפורשים.

בינוניכ-11 דקות קריאהעודכן 2026-10-30