JSON Toolkit

JSON Lines ו-NDJSON: נתוני זרימה

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

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

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

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

1. כללי הפורמט

JSON Lines (.jsonl) ו-NDJSON (.ndjson) הם כמעט מילים נרדפות, והכללים קצרים בשני המקרים:

  1. הקידוד הוא UTF-8, בלי BOM;
  2. כל שורה מחזיקה ערך JSON שלם ותקין אחד;
  3. השורות מופרדות ב-\n; השורה האחרונה יכולה להסתיים בשורה חדשה או לא;
  4. אסור שיופיע שורה חדשה לא מהומלטת בתוך שורה — שורה חדשה בתוך מחרוזת חייבת להיכתב כ-\n;
  5. שורות ריקות בדרך כלל מתעלמים מהן, אבל מימוש קפדני צריך להתייחס אליהן כלא תקינות.
{"time":"2026-10-30T10:41:45+08:00","level":"info","message":"boot"}
{"time":"2026-10-30T10:41:46+08:00","level":"warn","message":"slow query","costMs":812}
{"time":"2026-10-30T10:41:47+08:00","level":"error","message":"timeout","traceId":"a1b2"}

ההבדל העדין בין השניים: אתר JSON Lines מדגיש "ערך אחד לשורה, והערך יכול להיות כל JSON (כולל סקלרים)", בעוד שמפרט NDJSON מוסיף כללי מימוש קפדניים יותר כמו "אין שורות ריקות ואין סקלרים ברמה העליונה". בהנדסה, פעולה לפי הקבוצה הקפדנית יותר מונעת צרות.

2. השוואה למערך JSON

ממד מערך JSON JSON Lines
הוספת רשומה כתיבה מחדש של כל הקובץ (שינוי ה-] הסוגר) רק הוספת שורה אחת
קריאה בזרימה דורש מפרסר זרימה שעוקב אחרי עומק סוגריים פיצול לפי שורה
רשומה פגומה אחת כל הקובץ הופך לבלתי ניתן לפרסור רק השורה ההיא מושפעת
אימות הקובץ כולו אימות הכל במעבר אחד רק שורה אחר שורה
דחיסה גודל דומה אחרי gzip זהה, ואפשר לדחוס בלוקים במקביל
ביטוי קינון נתמך באופן מקורי כל שורה יכולה להתקנן פנימית; שורות אינן קשורות זו לזו
צרכנים טיפוסיים כל כלי JSON מערכות לוגים, צינורות ביג-דאטה, מערכי אימון LLM

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

3. קריאה: זרימה שורה אחר שורה

ב-Node, הגישה הפשוטה ביותר היא readline:

import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

/**
 * Reads a JSON Lines file line by line and invokes a callback for each record.
 * @param filePath Path to the file
 * @param onRecord Handler for a single record
 * @returns Statistics containing the number of successful and failed lines
 */
async function readJsonLines(
  filePath: string,
  onRecord: (record: unknown, lineNumber: number) => void,
): Promise<{ okCount: number; badCount: number }> {
  const lineReader = createInterface({
    input: createReadStream(filePath, { encoding: 'utf8' }),
    crlfDelay: Infinity,
  });

  let okCount = 0;
  let badCount = 0;

  for await (const rawLine of lineReader) {
    const line = rawLine.trim();
    if (!line) {
      continue;
    }
    try {
      onRecord(JSON.parse(line), okCount + badCount + 1);
      okCount += 1;
    } catch {
      badCount += 1;
    }
  }

  return { okCount, badCount };
}

השימוש בזיכרון של הקוד הזה תלוי רק בגודל שורה בודדת, לא בגודל הקובץ הכולל — זהו הערך הגדול ביותר של פורמט מופרד שורות.

3.1 כשצריך רק כמה שדות, אל תפרסרו קודם עם JSON.parse

אם בכל רשומה יש 50 שדות ואתם צריכים רק 3, JSON.parse בונה אובייקטים עבור כל 50. מפרסר זרימה (stream-json, clarinet) יכול לחלץ רק את שדות היעד, במחיר מורכבות מימוש גבוהה יותר. כלל אצבע: כשרשומה בודדת קטנה מכמה מאות KB, קריאה ישירה ל-JSON.parse היא בדרך כלל עדיין העסקה הטובה יותר.

3.2 טקסט סיני ו-BOM

קובצי .jsonl שנוצרים ב-Windows נושאים לעתים קרובות BOM, ו-JSON.parse על השורה הראשונה ייכשל בוודאות. הסירו את ה-BOM לפני הקריאה, או בדקו את שלושת הבתים הראשונים בזמן זרימת ה-chunk הראשון.

4. כתיבה: הוספות ואטומיות

import { appendFile } from 'node:fs/promises';

/**
 * Appends a single JSON Lines record.
 * @param filePath Target file
 * @param record The record object to write
 * @returns A promise that resolves when the write completes
 */
async function appendRecord(filePath: string, record: unknown): Promise<void> {
  const line = `${JSON.stringify(record)}\n`;
  await appendFile(filePath, line, { encoding: 'utf8' });
}

שני דברים שכדאי לשים לב אליהם:

  1. הפלט של JSON.stringify אינו מכיל שורות חדשות (אלא אם \n של מחרוזת מהומלט לשני תווים), כך ששורה אחת היא תמיד בדיוק רשומה אחת;
  2. כשכמה תהליכים מוסיפים במקביל, write בודד שקטן מ-PIPE_BUF (בדרך כלל 4096 בתים בלינוקס) הוא אטומי; כל דבר גדול יותר אינו מובטח. בתרחישי מקביליות גבוהה, עברו ל"קובץ אחד לכל תהליך, מיזוג אחרי שטיפה לדיסק", או השתמשו באחסון עם טרנזקציות.

5. טיפול בשורות פגומות

זו ההחלטה שפורמט מופרד שורות מכריח אתכם לקבל מראש:

אסטרטגיה מתאים ל עלות
דילוג וספירה נתונים שסובלים פערים, כמו לוגים ואירועי אנליטיקה נתונים הולכים לאיבוד בשקט, ולכן התראות חובה
כשל מיידי נתונים שאסור לאבד, כמו חיוב וחשבוניות שורה פגומה אחת חוסמת את כל האצווה
ניתוב לקובץ dead-letter מקרים שדורשים השלמה מאוחר יותר אחסון נוסף ותהליך הרצה חוזרת

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

6. דחיסה ופיצול

  • gzip דוחס .jsonl ביחס גבוה ועקבי (שמות שדות חוזרים), ולכן .jsonl.gz מומלץ;
  • gzip דוחס בבלוקים ותומך באופן מקורי בפריסה בזרימה, כך שאין צורך לפרוס את כל הקובץ;
  • פצלו קבצים גדולים לפרוסות של בערך 1GB, עם מספרי סדר בשמות, כדי להקל על עיבוד מקבילי והעברה הניתנת להמשך;
  • לעולם אל תעבדו מראש .jsonl על ידי "מיזוג למערך" — זה זורק את כל היתרונות שלו.

7. פשרות מול פורמטי ביג-דאטה אחרים

פורמט מבנה כולל סכימה טוב ל
JSON Lines עצמאי בכל שורה לא (דורש חוזה חיצוני) לוגים, ייבוא וייצוא, נתוני אימון
CSV טבלאי לא דוחות, החלפה עם Excel
Parquet עמודתי כן שאילתות אנליטיות, מערכי נתונים עצומים offline
Avro מבוסס שורות כן הודעות Kafka, אבולוציית סכימה

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

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

  • [ ] סיומת הקובץ עקבית (.jsonl או .ndjson) ואינה מתערבבת לעולם;
  • [ ] הכותב מבטיח "רשומה אחת לשורה", כאשר שורות חדשות בתוך מחרוזות כבר מהומלטות;
  • [ ] הקורא מסיר את ה-BOM, מדלג על שורות ריקות ומתחזק מספרי שורות;
  • [ ] אסטרטגיית השורות הפגומות מוגדרת ומנוטרת במפורש (שורות פגומות / סך השורות);
  • [ ] קבצים גדולים מפוצלים לפי גודל ומשולבים עם gzip וקריאה מקבילית;
  • [ ] במקום שבו קיים חוזה שדות, מסופקת סכימה נוספת והיא מאומתת שורה אחר שורה.

FAQ

איך בוחרים בין מערך JSON ל-JSON Lines?

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

למה שורה פגומה אחת לא שוברת את כל הקובץ?

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

כדאי להמיר JSON Lines למערך JSON?

אפשר: לקרוא שורה אחר שורה, לקרוא ל-JSON.parse על כל רשומה ולאסוף אותן למערך. זה דורש שכל התוכן ייכנס לזיכרון ומוותר על קריאה בזרימה ועל הוספת שורה אחת. עיבוד מראש של .jsonl למערך זורק את כל היתרונות של הפורמט, ולכן הוא הגיוני רק למסירה חד-פעמית שבה הנתונים נכנסים לזיכרון.

מקורות

כלים מתאימים

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

JSON5 ו-JSONC: JSON עם הערות

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

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

ניתוח, רינדור והזרמה של JSON גדול

ניתוח הוא לעתים קרובות לא צוואר הבקבוק; רינדור והעתקה כן. בצעו אופטימיזציה לאורך ארבע השכבות "ניתוח → מעבר → רינדור → העברה" כדי שהמאמץ לא ילך לאיבוד.

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

עבודה מעשית עם קבצי JSONL: פתיחה, המרה ואיתור שורות פגומות

לתת קובץ .jsonl שלם ל-JSON.parse ולקבל שגיאה כמעט אף פעם לא אומר שהקובץ הרוסי — זה אומר שאסטרטגיית הקריאה שגויה. המדריך הזה מכסה את הדרכים הסטנדרטיות לבדיקה וקריאה שורה־שורה של JSONL, פקודות המרה דו־כיוונית מול מערכי JSON, טבלה של שישה סוגי שורות פגומות ואת הדרך הנכונה לצרוך NDJSON מתשובות HTTP בסטרימינג.

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