JSON Toolkit

נקודות מפתח לתכנון חוזי נתונים ב-JSON API

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

בינוניכ-13 דקות קריאהפורסם 2026-10-30עודכן 2026-10-30
שער עבור נקודות מפתח לתכנון חוזי נתונים ב-JSON API

שורה תחתונה: נעלו בחוזה את מעטפת התשובה, פורמט השגיאה, ייצוג המזהים והזמן והסמנטיקה של null מול היעדר לפני שאתם מתווכחים על עימוד ופרטי שמות; תמיד עטפו את הרמה העליונה במעטפת data כדי שעימוד ומזהי מעקב יוכלו להיווסף אחר כך בלי לשבור קוראים קיימים.

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

1. לעטוף את התשובה או לא

שלושה סגנונות:

// סגנון A: תשובה חשופה
{ "id": 1, "name": "张三" }

// סגנון B: מעטפת data
{ "data": { "id": 1, "name": "张三" } }

// סגנון C: data + מטא-נתונים
{ "data": { "id": 1, "name": "张三" }, "meta": { "requestId": "a1b2" } }
סגנון יתרונות חסרונות
תשובה חשופה הפשוט ביותר, רמה אחת פחות לפרונט אי אפשר לצרף מטא-נתונים; מערך ברמה העליונה נושא סיכוני חטיפת JSON ואבולוציה
מעטפת data קל להוסיף meta / errors / links רמת קינון נוספת, הפרונט צריך לכתוב res.data.data
data + מטא-נתונים observability הטוב ביותר דורש הסכמה קפדנית על אילו שדות שייכים ל-meta

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

2. פורמט שגיאה: העתיקו את RFC 9457

המצאת מבנה שגיאה משלכם היא מקור עצום לעלות תיאום. RFC 9457 (שמחליף את RFC 7807) מגדיר application/problem+json:

{
  "type": "https://bbdrug.com/problems/insufficient-balance",
  "title": "余额不足",
  "status": 422,
  "detail": "账户余额 12.00 元,本次扣款需要 30.00 元",
  "instance": "/accounts/A1001/payments/p-889",
  "balance": "12.00",
  "required": "30.00"
}
שדה משמעות נדרש
type URI יציב לסוג הבעיה מומלץ
title סיכום קצר קריא לבני אדם מומלץ
status קוד סטטוס HTTP מומלץ
detail הסבר ספציפי למה שקרה הפעם אופציונלי
instance מופע המשאב שבו הייתה הבעיה אופציונלי
שדות הרחבה שדות עסקיים שרירותיים אופציונלי

שדות הרחבה (כמו balance / required שלמעלה) מותרים במפרט, והפרונט יכול להשתמש בהם להודעות מדויקות במקום לפרסר את טקסט ה-detail.

3. עימוד: cursor עדיף על offset

// עימוד offset
{ "data": [], "page": { "offset": 40, "limit": 20, "total": 137 } }

// עימוד cursor
{ "data": [], "page": { "nextCursor": "eyJpZCI6MTAwfQ", "hasMore": true } }
ממד עימוד offset עימוד cursor
קושי מימוש נמוך (LIMIT/OFFSET) בינוני (דורש מפתח מיון יציב)
ביצועים בדפים עמוקים גרועים (ככל שה-offset גדל, זה נהיה איטי יותר) טובים
כשהנתונים משתנים כפילויות או פריטים חסרים יציב
קפיצה לדף אפשרית לא אפשרית
דורש ספירה כוללת דורש ספירה נוספת לא נדרש

המלצה: השתמשו בעימוד offset לטבלאות ניהול (הן צריכות קפיצות דף וסכומים), ובעימוד cursor לפידים אינסופיים שמיועדים למשתמשים.

4. מזהים וזמן

4.1 תמיד השתמשו במחרוזות למזהים

הנימוק כוסה במאמר על טיפוסים: בצד של JavaScript, ערכים מעל 2^53 מאבדים דיוק בשקט. גם אם המזהה הנוכחי הוא מספר שלם קטן שעולה אוטומטית, שימוש במחרוזות משאיר מקום למעבר למזהי snowflake בהמשך.

{ "id": "1000000000000000001" }

4.2 השתמשו במחרוזות RFC 3339 לזמן

{
  "createdAt": "2026-10-30T10:41:45+08:00",
  "updatedAt": "2026-10-30T02:41:45Z"
}
  • אל תשתמשו במספרי חותמת זמן במילישניות: קשים לקריאה, והיחידה (שניות/מילישניות) נטעית לעתים קרובות;
  • ציינו את אזור הזמן במפורש: או עם Z או עם offset; לעולם אל תשתמשו ב"שעה מקומית בלי אזור זמן";
  • תנו שמות אחידים לשדות כמו xxxAt כדי שיהיה ברור במבט חטוף שזה רגע בזמן.

5. null והיעדר

חייבים לבחור אחת ולכתוב אותה בתיעוד:

גישה ביטוי "אין ערך" ביטוי "אין לשנות"
גישה A ערך השדה הוא null השמיטו את השדה בבקשה
גישה B השדה לא קיים השמיטו את השדה בבקשה

הבעיה בגישה A היא ש"הושמט" ו"נשלח כ-null" משמעותם שונה ב-PATCH, וקל לפרש לא נכון; הבעיה בגישה B היא ש-JSON.stringify של הפרונט משמיט שדות שערכם undefined, וזה במקרה מתאים לסמנטיקה.

המלצה מעשית:

  • השתמשו בגישה A לגופי תשובה (null פירושו בבירור אין ערך, והפרונט לא צריך להבחין בין undefined);
  • השתמשו בגישה B לגופי בקשה (במיוחד PATCH), בשילוב Merge Patch או JSON Patch כדי להפוך את הסמנטיקה למפורשת.

6. שמות שדות ומבנה

החלטה בחירות נפוצות הסתייגות
סגנון שמות camelCase (אקוסיסטם JS) / snake_case (Python, מסדי נתונים) אחיד בפרויקט, אל תערבבו
ערכי enum אותיות קטנות עם קו תחתון, "pending_review" אל תשתמשו במספרים; קשים לקריאה
שדות בוליאניים isActive / enabled אל תשתמשו ב-status: 0/1
שדות מערך שמות עצם ברבים, items החזירו [] למערכים ריקים, לעולם לא null
עומק קינון לא יותר מ-3 רמות עמוק מזה, פצלו אותו או הציעו פרמטר expand

החזרת [] במקום null עבור מערכים ריקים חשובה מאוד: data.items.map(...) בפרונט לא יקרוס בגלל null.

7. אסטרטגיית אבולוציה

שינוי שובר תאימות איך לטפל
הוספת שדה אופציונלי לא שחררו ישירות
הוספת שדה נדרש כן הוסיפו ערך ברירת מחדל, או שחררו לפי גרסה
הסרת שדה כן סמנו אותו כ-deprecated קודם, עקבו אחרי הקוראים, ואז הסירו
שינוי טיפוס שדה כן הוסיפו שם שדה חדש וכתבו כפול בזמן המעבר
שינוי קבוצת ערכי enum תלוי ללקוח חייב להיות fallback לערכים לא מוכרים
שינוי סמנטיקת שדה כן התייחסו אליו כשדה חדש

שלושה כללי משמעת:

  1. לקוחות חייבים להיות סובלניים לשדות לא מוכרים (אל תסגרו את עצמכם עם additionalProperties: false);
  2. ללקוחות חייב להיות ענף fallback לערכי enum לא מוכרים;
  3. שמרו שדות deprecated לפחות גרסה major אחת, וסמנו אותם עם Deprecation בכותרות התשובה או בתיעוד.

8. דוגמה מלאה

{
  "data": [
    {
      "id": "1001",
      "name": "JSON 权威指南",
      "price": "89.50",
      "tags": ["json", "spec"],
      "publishedAt": "2026-09-01T00:00:00+08:00",
      "author": null
    }
  ],
  "page": { "nextCursor": null, "hasMore": false },
  "meta": { "requestId": "req-7f3a", "tookMs": 12 }
}

התשובה הזו משקפת את כל המוסכמות שלמעלה: מעטפת data, מזהים כמחרוזות, סכומים כמחרוזות, חותמות זמן עם אזור זמן, null שפירושו חסר, מערך ריק, עימוד cursor, ו-meta למידע observability.

9. צ'קליסט

  • [ ] הרמה העליונה אינה מערך; השתמשו במעטפת data אחידה;
  • [ ] תשובות שגיאה עוקבות אחרי RFC 9457 וכוללות type יציב;
  • [ ] צורת העימוד מתאימה לתרחיש העסקי;
  • [ ] מזהים הם מחרוזות, והזמן משתמש ב-RFC 3339;
  • [ ] הסמנטיקה של null והיעדר כתובה בתיעוד;
  • [ ] סגנון השמות אחיד בפרויקט, ו-enums משתמשים במחרוזות באותיות קטנות;
  • [ ] ללקוח יש fallback לשדות לא מוכרים ולערכי enum לא מוכרים;
  • [ ] שינויים שוברים עוברים דרך גרסאות או מעבר בכתיבה כפולה.

FAQ

איך אתכנן JSON API שניתן לאבולוציה?

עטפו רשימות ופרטים באופן אחיד במעטפת data ולעולם אל תהפכו את הרמה העליונה למערך, כדי שאפשר יהיה להוסיף עימוד ומזהי מעקב בלי לשבור את החוזה. השתמשו במזהים כמחרוזות ובחותמות זמן RFC 3339, וכתבו בתיעוד את הסמנטיקה של null מול היעדר. השאירו שדות חדשים כאופציונליים ועצמאיים סמנטית, ותנו ללקוחות fallback לשדות ולאומי enum לא מוכרים.

למה לא להמציא פורמט שגיאה מותאם?

המצאת מבנה שגיאה משלכם היא מקור עצום לעלות תיאום. העתיקו את application/problem+json של RFC 9457, זהו בעיות עם type יציב, והתירו שדות הרחבה כמו balance ו-required, כדי שהפרונט יוכל לבנות הודעות מדויקות בלי לפרסר את טקסט ה-detail.

האם כל endpoint צריך להשתמש בעימוד cursor?

לא. טבלאות ניהול צריכות קפיצות דף וסכומים, ולכן עימוד offset מתאים יותר; פידים אינסופיים שמיועדים למשתמשים צריכים להשתמש בעימוד cursor, שנשאר יציב בדפים עמוקים ונמנע מכפילויות או פערים כשהנתונים משתנים. בחרו לפי האם התרחיש צריך קפיצות דף וסכומים.

מקורות

כלים מתאימים

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

שישה טיפוסים, דיוק מספרים ומלכודות קידוד

ל-JSON יש רק שישה טיפוסים, אבל כל אחד מהם יכול לעצב מחדש את הנתונים שלכם בשקט. מאמר זה מכסה במעבר אחד את המלכודות של מספרים, מחרוזות ו-null.

מתחילכ-12 דקות קריאהעודכן 2026-10-30

JSON Pointer, Patch ו-Merge Patch

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

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

ניהול JSON עבור תצורה, fixtures ונתונים מקומיים

תצורה חייבת להיות בשכבות ומאומתת; fixtures חייבים להיות דטרמיניסטיים, קריאים וניתנים ל-diff. אם שני סוגי הקבצים האלה מנוהלים גרוע, עלות פתרון התקלות נשארת גבוהה זמן רב.

מתחילכ-10 דקות קריאהעודכן 2026-10-30