JSON Toolkit

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

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

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

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

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

1. תצורה: מבנה תלת-שכבתי

שכבה מקור מי מתחזק נכנס ל-commit
ברירות מחדל קונסטנטות בקוד / default.json מפתחים כן
תצורת סביבה config/production.json, משתני סביבה תפעול כן (בלי סודות)
עקיפות מקומיות .env.local, config/local.json יחידים לא (מוחרג ב-gitignore)

סדר המיזוג: ברירות מחדל → סביבה → מקומי, כשכל שכבה מאוחרת דורסת את הקודמת. מזגו רק בעומק שהעסק צריך; אל תמזגו עמוק באופן עיוור — מיזוג עמוק הופך בעיות כמו "מערכים התחברו בצורה לא צפויה" לקשות מאוד לזיהוי.

/**
 * ממזג שכבות תצורה לפי עדיפות; שכבות מאוחרות דורסות קודמות, ומערכים מוחלפים במלואם ולא מחוברים.
 * @param layers שכבות תצורה מהעדיפות הנמוכה לגבוהה
 * @returns אובייקט התצורה הממוזג
 */
function mergeConfigLayers(layers: Array<Record<string, unknown>>): Record<string, unknown> {
  return layers.reduce<Record<string, unknown>>((mergedConfig, currentLayer) => {
    for (const [configKey, configValue] of Object.entries(currentLayer)) {
      const isPlainObject =
        typeof configValue === 'object' && configValue !== null && !Array.isArray(configValue);
      const existingValue = mergedConfig[configKey];

      if (isPlainObject && typeof existingValue === 'object' && existingValue !== null) {
        mergedConfig[configKey] = mergeConfigLayers([
          existingValue as Record<string, unknown>,
          configValue as Record<string, unknown>,
        ]);
        continue;
      }

      // מערכים וסקלרים תמיד מוחלפים במלואם כדי למנוע חיבור אלמנטים בטעות
      mergedConfig[configKey] = configValue;
    }
    return mergedConfig;
  }, {});
}

2. אמתו באתחול, אל תחכו לריצה

עלותה של שגיאת תצורה גדלה אקספוננציאלית עם כמה שהיא מתגלה מאוחר:

מתי היא מתגלה עלות
באתחול שורת לוג אחת, אתחול וממשיכים
בבקשה הראשונה חלק מהתכונות נכשלות, שעה של דיבוג
כשה-job מתוזמן רץ התראות באמצע הלילה
ביום ההתחשבנות תקרית התאמות
import Ajv2020 from 'ajv/dist/2020';
import configSchema from './config.schema.json' with { type: 'json' };

const ajv = new Ajv2020({ allErrors: true, strict: true });

/**
 * מאמת את התצורה וזורק מיד במקרה כשל, ומיישם fail fast.
 * @param rawConfig התצורה הממוזגת הגולמית
 * @returns התצורה המאומתת
 */
export function assertConfig(rawConfig: unknown): Record<string, unknown> {
  const validate = ajv.compile(configSchema);
  if (!validate(rawConfig)) {
    const messages = (validate.errors ?? []).map(
      (errorItem) => `${errorItem.instancePath || '/'} ${errorItem.message ?? ''}`,
    );
    throw new Error(`配置校验失败:\n${messages.join('\n')}`);
  }
  return rawConfig as Record<string, unknown>;
}

עקרונות תומכים:

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

3. איזה פורמט להשתמש לקובצי תצורה

קובץ פורמט סיבה
package.json JSON סטנדרטי מנהל החבילות מקבל רק את זה
tsconfig.json JSONC הערות מותרות, נתמך רשמית
.vscode/settings.json JSONC כמו למעלה
תצורה עסקית YAML או JSONC צריכה הערות שמסבירות מה כל פריט אומר
רשימת משתני סביבה .env זוגות מפתח-ערך שטוחים, קלים להזרקה

מבחן מעשי: האם הקובץ הזה ייקרא על ידי כלים לא אנושיים? אם כן → JSON סטנדרטי; אם רק הקוד שלכם קורא אותו → דיאלקט זה בסדר.

4. Fixtures לבדיקות: ספריות ושמות

tests/fixtures/
├─ valid/
│  ├─ order-minimal.json
│  ├─ order-full.json
│  └─ order-with-null-fields.json
├─ invalid/
│  ├─ order-missing-id.json
│  ├─ order-price-as-number.json
│  └─ order-nested-broken.json
└─ edge/
   ├─ big-integer.json
   ├─ emoji-and-surrogate.json
   └─ deep-nesting-64.json

מוסכמת שמות טובה היא object-scenario: order-missing-field שימושי הרבה יותר מ-test1.json. שמות קבצים לא-ASCII גורמים לפעמים לבעיות קידוד בכלי שרשרת חוצי-פלטפורמות, לכן שמרו על שמות קבצים באנגלית בעוד שהתוכן יכול להישאר בסינית.

4.1 שלושה סוגי מקרים, שלושה סוגי ציפיות

ספרייה ציפייה מטרה
valid/ האימות עובר זרימה ראשית, יצירת טיפוסים
invalid/ האימות נכשל, והשגיאה מצביעה על השדה הצפוי טיפול בשגיאות, העתק הודעה
edge/ ההתנהגות מפורשת (לעבור או להיכשל זה בסדר, אבל חייב להיות מאומת) הגנת רגרסיה

JSONTestSuite משתמש בקידומות y_ / n_ / i_ לאותם שלושה סוגים (צריך לקבל / צריך לדחות / תלוי מימוש), ואפשר לשאול את מוסכמת השמות הזו ישירות.

5. דטרמיניזם: הפכו fixtures לניתנים ל-diff

fixtures לא יציבים הופכים את סקירת הקוד לחסרת תועלת:

מקור רעש איך לטפל
הזמן הנוכחי קבעו אותו לקונסטנטה, למשל 2026-01-01T00:00:00Z
מזהים אקראיים / UUIDs השתמשו ברצף עולה או בערכים קבועים
סדר מפתחות מיינו לפי אלף-בית בזמן היצירה
עיצוב מספרים עשרוניים שמרו אחיד שני מקומות עשרוניים או המירו למחרוזות
אזור זמן השתמשו ב-UTC בכל מקום
הזחה קבועה על 2 רווחים, נבדקת על ידי Prettier ב-CI
/**
 * מיין מפתחות רקורסיבית כדי להפיק טקסט JSON יציב וניתן ל-diff.
 * @param value הערך לסריאליזציה
 * @param indentWidth רוחב ההזחה
 * @returns מחרוזת JSON עם סדר יציב
 */
function toStableJson(value: unknown, indentWidth = 2): string {
  /**
   * מיין רקורסיבית את מפתחות האובייקט.
   * @param input כל ערך JSON
   * @returns ערך חדש עם מפתחות ממוינים
   */
  const sortKeysDeep = (input: unknown): unknown => {
    if (Array.isArray(input)) {
      return input.map((item) => sortKeysDeep(item));
    }
    if (typeof input === 'object' && input !== null) {
      return Object.fromEntries(
        Object.keys(input)
          .sort()
          .map((entryKey) => [entryKey, sortKeysDeep((input as Record<string, unknown>)[entryKey])]),
      );
    }
    return input;
  };

  return JSON.stringify(sortKeysDeep(value), null, indentWidth);
}

6. מיסוך וציות

נתונים אמיתיים לא יכולים לשמש ישירות כ-fixtures:

  • שמות → ממלאי מקום כמו "John Doe" ו-"Jane Roe";
  • מספרי טלפון → מספרים פיקטיביים בבירור כמו 13800138000;
  • מספרי תעודת זהות → מספרים מזויפים עם פורמט נכון אבל ספרת ביקורת לא חוקית;
  • כתובות → שמרו את מבנה המחוז/עיר, השמיטו את מספר הבית;
  • סכומים → שמרו את סדר הגודל, אבל לעולם אל תשמרו מידע חשבון אמיתי.

מוטב שלצוות יהיה scripts/mask-fixture.mjs שרץ על דגימת פרודקשן מיוצאת כדי ליצור גרסה ממוסכת, כדי להימנע מ"לשנות שלושה מקומות ביד ולשכוח את השאר".

7. העברה וגרסאות

גם fixtures מתיישנים. הוסיפו שדה schemaVersion ל-fixture, או הניחו קובץ VERSION בספרייה:

{
  "schemaVersion": 3,
  "id": "1001",
  "amount": "89.50"
}

כשחוזה ה-API משודרג:

  1. הוסיפו ספריית fixture חדשה (v3/); אל תשנו fixtures ישנים במקום;
  2. שמרו את ה-fixtures הישנים לרגרסיה עד שגרסת ה-API הישנה מוסרת;
  3. הריצו את שתי הגרסאות ב-CI כדי לוודא תאימות לאחור.

8. צ'קליסט

  • [ ] מבנה התצורה התלת-שכבתי ברור, ועקיפות מקומיות לא נכנסות ל-commit;
  • [ ] האימות קורה באתחול, וכשל יוצא מיד;
  • [ ] סודות מגיעים רק ממשתני סביבה; קובצי תצורה מכילים רק ממלאי מקום;
  • [ ] fixtures מחולקים לספריות valid/invalid/edge, והשמות מבטאים את התרחיש;
  • [ ] fixtures דטרמיניסטיים (זמן, מזהים וסדר קבועים) וניתנים ל-diff;
  • [ ] נתונים אמיתיים עברו עיבוד בסקריפט המיסוך;
  • [ ] שדרוגי חוזה מוסיפים ספריית גרסה חדשה במקום עריכה במקום.

FAQ

איך צריך לסדר ולמזג שכבות תצורה?

השתמשו בשלוש שכבות — ברירות מחדל, תצורת סביבה ועקיפות מקומיות — ומזגו ברירות מחדל → סביבה → מקומי, כשכל שכבה מאוחרת דורסת את הקודמת. מזגו רק בעומק שהעסק צריך והחליפו מערכים וסקלרים במלואם, כדי שמיזוג עמוק לא יחבר מערכים בצורה לא צפויה. הוסיפו את קובץ העקיפה המקומי ל-gitignore במקום לבצע commit להגדרות אישיות.

למה לאמת תצורה באתחול?

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

האם להשתמש בנתוני פרודקשן ישירות כ-fixtures לבדיקות?

לא. שמות, מספרי טלפון, מספרי תעודות זהות, כתובות וסכומים כולם צריכים ממלאי מקום או ערכים פיקטיביים בבירור, ולצוות צריך להיות scripts/mask-fixture.mjs שרץ על ייצוא פרודקשן כדי ליצור גרסה ממוסכת, כדי להימנע מעריכות ידניות שמפספסות משהו.