JSON Toolkit

שאלות נפוצות על שאילתות ואימות: JSONPath ו-Schema

שורה תחתונה: האם שאילתות נתיב צריכות להשתמש ב-JSONPath או ב-Pointer, איך בוחרים בין Patch ל-Merge Patch, מה Schema יכול לחסום, ואיך בוחרים גרסת draft — הכל מוסבר במעבר אחד.

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

שורה תחתונה: השתמשו ב-JSONPath כדי למצוא צמתים לפי תנאי, ב-JSON Pointer או ב-JSON Patch כדי לכתוב לכתובת מדויקת ולערוך אותה, וב-Merge Patch כדי למזג קונפיגורציה; הצמידו Schemas חדשים ל-2020-12 והפעילו allErrors כדי שכל כשל יחזור במעבר אחד.

שאילתה היא "למצוא"; אימות הוא "לוודא שזה נכון". לכל אחד יש תקן משלו, אז אל תערבבו ביניהם.

1. שאילתות נתיב

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

זה תלוי אם אתם "מוצאים לפי תנאי" או "שולפים לפי מיקום".

ממד JSONPath JSON Pointer
תקן RFC 9535 (2024) RFC 6901 (2013)
ביטוי $.users[?(@.age > 18)].id /users/0/id
תווים כלליים רקורסיה תומך ב-*, .. לא נתמך
סינון מותנה נתמך לא נתמך
תוצאה קבוצת צמתים (אולי כמה) מיקום יחיד
כושר ביטוי חזק, אבל עם מורשת כבדה של הבדלי מימוש חלש, אבל דטרמיניסטי לחלוטין
שימוש טיפוסי חילוץ לוגים, דיבוג, חקר נתונים JSON Patch, כתובות קונפיגורציה, נתיבי שגיאה

כלל הבחירה פשוט:

  • צריך "להשיג את כל הצמתים שמתאימים לתנאי" → JSONPath;
  • צריך "לנעוץ מיקום מדויק אחד ולהוסיף, להסיר או לשנות אותו" → JSON Pointer;
  • השתמשו בהם יחד: מצאו את הצומת עם JSONPath ותארו את מיקומו עם Pointer.

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

מה ההבדל בין $.. ל-$.*?

$.. הוא ירידה רקורסיבית שעוברת כל רמה; $.* מחזיר רק את הילדים הישירים של הרמה הנוכחית.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → שני האובייקטים {id:1, child:{...}} ו-{id:3};
  • $..id → שלושה ערכים: 1, 2, 3;
  • $.*.id → 1 ו-3, בלי 2 המקונן.

ירידה רקורסיבית נוחה אבל יקרה: $.. עובר את כל העץ, שזה O(n) על מסמך גדול, והוא בקלות אוסף שדות עמוקים שלא התכוונתם אליהם. אם רמה קונקרטית עובדת, הימנעו מ-...

האם סינון יכול להשוות מחרוזות? האם הוא יכול להתאים עם regex?

השוואת מחרוזות עובדת; regex תלוי במימוש.

תחביר סטנדרטי (RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // compare with another node

השוואת מחרוזות משתמשת בגרשיים כפולים (גרשיים בודדים מותרים גם ב-RFC 9535, אבל חלק מהמימושים הישנים מקבלים רק גרשיים כפולים). השוואה מספרית פועלת לפי סמנטיקה מספרית; ערבוב מחרוזת עם מספר, כמו "10" > 9, הוא התנהגות בלתי מוגדרת בתקן, אז אל תסתמכו עליה.

לגבי regex, RFC 9535 מגדיר את הפונקציות match() ו-search(), אבל התמיכה בהן משתנה מאוד בין מימושים. הגישה הבטוחה: תנו ל-JSONPath לעשות סינון גס, ועשו את התאמת ה-regex בקוד אחרי שמקבלים את התוצאות.

האם להשתמש ב-JSON Patch או ב-Merge Patch?

שניהם מתארים "איך לשנות משהו", אבל כושר הביטוי והסמנטיקה שלהם שונים לחלוטין.

ממד JSON Patch (RFC 6902) Merge Patch (RFC 7386)
צורה מערך פעולות [{"op":"add",...}] אובייקט חלקי
פעולות נתמכות add / remove / replace / move / copy / test מיזוג בלבד (מחיקה דרך null)
כתובת לאלמנטי מערך כן, לפי אינדקס לא, המערך כולו מוחלף
אטומיות test מאפשר נעילה אופטימיסטית אין
גודל מסמך גדול אבל מדויק קטן אבל מוגבל בכושר ביטוי
מתאים ל עריכות עדינות, עדכוני API חלקיים מיזוג קונפיגורציה, שליחת טופס חלקית

קריטריונים להחלטה:

  • לשנות שדה של האלמנט השלישי במערך → JSON Patch;
  • למזג כמה רשומות קונפיגורציה לתוך הקונפיגורציה הקיימת → Merge Patch;
  • לקבל בקרת מקביליות של "היכשל אם הערך המקורי אינו X" → test של JSON Patch.

שימו לב למלכודת בסמנטיקת ה-null של Merge Patch: null פירושו "מחק את המפתח הזה", לא "קבע את הערך ל-null". אי אפשר לקבוע ערך ל-null עם Merge Patch.

2. אימות Schema

אילו בעיות JSON Schema פותר?

הוא הופך את "איך הנתונים אמורים להיראות" מהסכמה מילולית לחוזה שניתן לבדוק במכונה. השגיאות שהוא באמת יכול לחסום:

  • שדות חובה חסרים (required);
  • טיפוס שגוי, למשל מחרוזת במקום מספר (type);
  • ערכים מחוץ לטווח (minimum / maximum / minLength);
  • ערכים מחוץ לקבוצה המותרת (enum);
  • טיפוסי אלמנטים לא עקביים במערך או אורך מערך שגוי (items / minItems);
  • שדות חסרים במבנים מקוננים (אימות רקורסיבי דרך properties);
  • שדות עודפים (additionalProperties: false).

מה שהוא לא יכול לפתור: כללים עסקיים ("זמן הסיום חייב להיות אחרי זמן ההתחלה"), תנאים חוצי-שדות (חלקם אפשר לבטא עם if/then, אבל הקריאות נפגעת), ייחודיות במסד נתונים ובדיקות הרשאה.

אז Schema הוא שומר הסף של "צורת הנתונים", לא תחליף ללוגיקה עסקית.

האם להשתמש ב-draft-07 או ב-2020-12?

השתמשו ב-2020-12 לפרויקטים חדשים; במערכות קיימות, בדקו מה הוולידטור תומך בו.

גרסה מצב שינויים מרכזיים
draft-04 מיושן הרבה ספריות ישנות עדיין משתמשות בו כברירת מחדל
draft-07 תקן דה-פקטו התמיכה הרחבה ביותר, האקוסיסטם העשיר ביותר
2019-09 מעבר הציג $defs, unevaluatedProperties
2020-12 מומלץ כיום items פוצל ל-prefixItems + items, תומך ב-$dynamicRef

שתי מלכודות מעבר:

  1. תחביר tuple השתנה: ב-draft-07 items עם ערך מערך ביטא tuple; 2020-12 העביר זאת ל-prefixItems;
  2. $ref ומילות מפתח אחיות: ב-draft-07 מילות מפתח אחיות מתעלמות כשקיים $ref; 2020-12 מאפשר להן לדור יחד.

אם הוולידטור תומך רק ב-draft-07, כתיבת תחביר 2020-12 בכל זאת מתעלמת בשקט — והמקרה המסוכן ביותר הוא "האימות עובר" כשבעצם שום דבר לא אומת.

איך מייצרים טיפוסי TypeScript מ-Schema?

שני מסלולים: גזרו טיפוסים מה-Schema, או ייצרו את ה-Schema מהטיפוסים.

מ-Schema לטיפוסים (הבקאנד מגדיר את החוזה, החזית צורכת אותו):

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

המפתח הוא as const: בלעדיו, type: 'string' מוסק כ-string ולא כליטרל 'string', וההיסק מתפרק.

מטיפוסים ל-Schema (לחזית כבר יש טיפוסים והיא צריכה אימות בזמן ריצה):

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

היתרון של המסלול הזה הוא ש"הגדרה אחת מניבה גם טיפוסים וגם אימות בזמן ריצה"; המחיר הוא הכנסת DSL, וייתכן שה-Schema שנוצר נושא פרטי מימוש (למשל מבנה $ref לא אטרקטיבי).

איך מאתרים את השדה הספציפי כשהאימות נכשל?

הביטו ב-instancePath (2020-12) או ב-dataPath (draft-07) שהוולידטור מחזיר; זה JSON Pointer למיקום השגיאה.

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" אומר לכם ישירות: בשדה price של האלמנט השלישי במערך items יש טיפוס שגוי.

שתי הצעות מעשיות:

  1. החזירו את כל השגיאות בבת אחת במקום לזרוק על הראשונה — תיקון הכל במעבר אחד בחזית מהיר יותר מניסיונות הלוך ושוב;
  2. המירו את ה-Pointer לשפה פשוטה לפני שמציגים אותו למשתמשי קצה; /items/2/price מועיל למפתחים וחסר תועלת למשתמשים.

allErrors: true הוא המתג ברוב הוולידטורים (Ajv דורש הפעלה מפורשת); ברירת המחדל היא לרוב מצב fail-fast.

האם להפעיל additionalProperties: false?

הפעילו אותו לחוזים פנימיים; היזהרו ב-API ציבוריים.

ברגע שהוא מופעל, כל שדה שלא הוכרז תחת properties נכשל באימות. היתרון הוא לתפוס טעויות הקלדה (userNmae) והעברה לא צפויה; החיסרון הוא שהוספת שדה במעלה הזרם שוברת צרכנים במורד הזרם, ומשמידה תאימות לאחור.

פשרה:

תרחיש המלצה
בין שירותים פנימיים הפעילו. שינויי שדות עוברים ניהול גרסאות
API חיצוניים ציבוריים כבו. אפשרו למעלה הזרם להוסיף שדות
צריכת נתוני צד שלישי כבו. אתם לא שולטים בצד השני
אימות קונפיגורציה הפעילו. טעות הקלדה בקונפיגורציה צריכה להיכשל מיד

איך מאמתים שאלמנטי מערך ייחודיים?

שתי סמנטיקות שונות — אל תבלבלו ביניהן:

{ "uniqueItems": true }

זה מסיר כפילויות של האלמנט כולו, ודורש שהמערך לא יכיל שני אלמנטים שווים לעומק.

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

הקטע שלמעלה רק מבטיח שהאובייקטים כשלם אינם כפולים; {"id":"a","name":"x"} ו-{"id":"a","name":"y"} לא נחשבים כפולים.

כדי להסיר כפילויות לפי שדה מסוים, Schema לא יכול לבטא זאת; צריך לאמת בשכבת העסקים:

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id 重复');
}

האם format באמת מאמת אימיילים ותאריכים?

לא בהכרח. במפרט, format הוא "הערה" כברירת מחדל ולא "הצהרה", כך שהוולידטור עשוי לאסוף אותו בלי לאמת אותו.

  • Ajv דורש התקנה והגדרה של החבילה הנפרדת ajv-formats, אחרת format: "email" הוא למעשה חסר תוקף;
  • חלק מהוולידטורים מתייחסים ל-format כהצהרה שמופעלת כברירת מחדל, כך שההתנהגות לא עקבית;
  • גם כשמופעל, ה-regex של email רופף מאוד — a@b עובר.

מסקנה: אל תתייחסו ל-format כאימות חזק. עשו אימות קפדני של אימיילים, כתובות URL ותאריכים בקוד העסקי; שמירת format ב-Schema כתיעוד שווה משהו, אבל אל תצפו שהוא יעצור נתונים מזוהמים.

3. עיון מהיר

שאלה מסקנה
מצא צמתים לפי תנאי JSONPath
כתובת מדויקת, החלת טלאים JSON Pointer
שנה אלמנטי מערך JSON Patch, Merge Patch לא יכול
מזג קונפיגורציה Merge Patch
גרסת Schema לפרויקטים חדשים 2020-12, שימו לב ל-prefixItems
ייצור טיפוסי TS מ-Schema json-schema-to-ts + as const
איתור שגיאות אימות קראו instancePath, הפעילו allErrors
הסרת כפילויות לפי שדה Schema לא יכול; השתמשו ב-Set בשכבת העסקים
format: email רוב הוולידטורים לא בודקים אותו כברירת מחדל, אז אל תסתמכו עליו