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';

/**
 * يقرأ ملف JSON Lines سطرًا بسطر ويستدعي دالة رد لكل سجل.
 * @param filePath مسار الملف
 * @param onRecord معالج سجل واحد
 * @returns إحصاءات تتضمن عدد الأسطر الناجحة والفاشلة
 */
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 يبني كائنات للخمسين كلها. يستطيع محلل متدفق (stream-json، clarinet) استخراج الحقول المستهدفة وحدها، بكلفة تعقيد تنفيذ أعلى. القاعدة العملية: عندما يكون السجل الواحد أقل من بضع مئات من الكيلوبايتات، فاستدعاء JSON.parse مباشرة هو عادة الصفقة الأفضل.

3.2 النصوص الصينية وBOM

ملفات .jsonl المولّدة على Windows تحمل BOM غالبًا، وJSON.parse على السطر الأول مضمون الفشل. أزل BOM قبل القراءة، أو افحص البايتات الثلاثة الأولى أثناء بث الجزء الأول.

4. الكتابة: الإضافات والذرّية

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

/**
 * يضيف سجل JSON Lines واحدًا.
 * @param filePath الملف الهدف
 * @param record كائن السجل المطلوب كتابته
 * @returns وعد يُحل عند اكتمال الكتابة
 */
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 بايت عادة على Linux) ذرّية؛ وما هو أكبر غير مضمون. للسياقات عالية التزامن، انتقل إلى "ملف واحد لكل عملية، يُدمج بعد التدفق إلى القرص"، أو استخدم تخزينًا بمعاملات.

5. معالجة الأسطر السيئة

هذا هو القرار الذي تفرضه عليك الصيغة محددة الأسطر مسبقًا:

الاستراتيجية تصلح لـ الكلفة
التخطي والعد بيانات تتحمل فجوات، مثل السجلات وأحداث التحليلات تُفقد البيانات بصمت، فالتنبيه إلزامي
الفشل فورًا بيانات لا يجوز فقدها، مثل الفوترة والفواتير سطر سيئ واحد يوقف الدفعة كلها
التوجيه إلى ملف رسائل ميتة حالات تحتاج إلى تعبئة لاحقة تخزين إضافي وعملية إعادة تشغيل

أيًا كان اختيارك، سجّل رقم السطر ومقتطفًا من المحتوى الأصلي، وإلا فلن تستطيع تحديد المشكلة لاحقًا. رقم السطر غير موجود في رسالة خطأ JSON.parse، فعليك صيانته بنفسك.

6. الضغط والتقسيم

  • يضغط gzip ملفات .jsonl بنسبة عالية ثابتة (تتكرر أسماء الحقول)، فيُنصح بـ .jsonl.gz؛
  • يضغط gzip على شكل كتل ويدعم فك الضغط المتدفق أصليًا، فلا حاجة إلى فك الملف كله؛
  • قسّم الملفات الكبيرة إلى شرائح بنحو 1GB، بأرقام تسلسلية في الأسماء، لتسهيل المعالجة المتوازية ونقل قابل للاستئناف؛
  • لا تعالج .jsonl مسبقًا بـ"دمجه في مصفوفة" — فذلك يهدر كل مزاياه.

7. المقايضات مع صيغ البيانات الضخمة الأخرى

الصيغة البنية تشمل المخطط جيدة لـ
JSON Lines مستقلة لكل سطر لا (تحتاج عقدًا خارجيًا) السجلات، الاستيراد والتصدير، بيانات التدريب
CSV جدولية لا التقارير، التبادل عبر Excel
Parquet عمودية نعم استعلامات تحليلية، مجموعات بيانات غير متصلة ضخمة جدًا
Avro قائمة على الصفوف نعم رسائل Kafka، تطور المخطط

ميزة JSON Lines أن أي لغة وأي أداة تستطيع قراءتها وكتابتها فورًا؛ وعيبها غياب المخطط والضغط العمودي. بمجرد أن تتجاوز البيانات جهازًا واحدًا وتحتاج إلى استعلامات عمودية متكررة، يحين وقت النظر في Parquet.

8. قائمة الفحص

  • [ ] امتداد الملف متسق (.jsonl أو .ndjson) ولا يختلط أبدًا؛
  • [ ] يضمن الكاتب "سجل واحد لكل سطر"، مع أسطر جديدة داخل النصوص مهروبة أصلًا؛
  • [ ] يزيل القارئ BOM ويتخطى الأسطر الفارغة ويحافظ على أرقام الأسطر؛
  • [ ] استراتيجية الأسطر السيئة محددة صراحة ومراقبة (الأسطر السيئة / الأسطر الكلية)؛
  • [ ] الملفات الكبيرة مقسمة بحسب الحجم ومقترنة بـ gzip والقراءة المتوازية؛
  • [ ] حيث يوجد عقد حقل، يُقدَّم مخطط إضافي ويُتحقق منه سطرًا بسطر.

الأسئلة الشائعة

كيف أختار بين مصفوفة JSON وJSON Lines؟

انظر إلى ما إذا كانت البيانات دفعة سجلات أم مستندًا واحدًا. اختر JSON Lines عندما تُضاف السجلات وتُقرأ كتدفق: الإضافة تعني كتابة سطر في النهاية، والقراءة تتطلب التقسيم بالسطر فقط. وأبقِ مصفوفة JSON عندما يجب التحقق من البنية كلها في تمريرة واحدة، لأن مصفوفة JSON تتحقق من كل شيء دفعة واحدة بينما JSON Lines يمكن التحقق منها سطرًا بسطر فقط.

لماذا لا يكسر سطر تالف واحد الملف كله؟

كل سطر في JSON Lines قيمة JSON مستقلة كاملة، والتحليل سطرًا بسطر بطبيعته، فتؤثر البيانات التالفة في ذلك السطر فقط. في مصفوفة JSON، يجعل خطأ صياغي واحد الملف كله غير قابل للتحليل، ولهذا بالضبط تناسب البيانات محددة الأسطر السجلات وأحداث التحليلات.

هل أحول JSON Lines إلى مصفوفة JSON؟

يمكنك: اقرأ سطرًا بسطر، واستدع JSON.parse على كل سجل، واجمعها في مصفوفة. يتطلب ذلك أن يتسع المحتوى كله للذاكرة، ويتخلى عن القراءة المتدفقة والإضافة بسطر واحد. معالجة .jsonl مسبقًا إلى مصفوفة تهدر كل مزايا الصيغة، فهي معقولة فقط لتسليم لمرة واحدة تناسب البيانات فيه الذاكرة.