JSON Toolkit

تحويل JSON إلى CSV والبيانات الجدولية والعكس

الخلاصة: CSV ثنائي الأبعاد، وJSON شجري. الجزء الصعب في التحويل ليس الشيفرة — بل تحديد مسبق لكيفية تسطيح المصفوفات وكيف تُحفظ الأنواع.

مبتدئقراءة نحو 10 دقيقةنُشر في 2026-10-30حُدِّث في 2026-10-30
غلاف تحويل JSON ↔ CSV والبيانات الجدولية

الخلاصة: استخدم المفاتيح المنقطة والمصفوفات المسلسلة إلى JSON عند التسطيح حين سيقرأ البشر التصدير، وقسّم إلى جدول أب وجداول مرتبطة عند تحميل قاعدة بيانات، ووسّع المصفوفات إلى صفوف إضافية فقط للجداول المحورية؛ وعند الاستعادة حوّل بتعريفات أعمدة صريحة لا باستنتاج شامل.

تسطيح الشجرة إلى جدول يفقد الهرمية دائمًا؛ واستعادة جدول إلى شجرة تخمّن الأنواع دائمًا. ويحتاج الاتجاهان إلى قواعدهما معلنة مسبقًا.

1. ما الذي تحدده CSV فعلًا

لا تتجاوز RFC 4180 بضع صفحات، وتحدد أقل مما يفترض كثيرون:

  1. كل سجل يشغل سطرًا واحدًا، وتُفصل الحقول بفواصل؛
  2. فاصل السطر هو CRLF، رغم أن \n مقبول عمليًا أيضًا؛
  3. الحقل الذي يحتوي فاصلة أو علامة اقتباس مزدوجة أو سطرًا جديدًا يجب أن يُحيط به علامتا اقتباس مزدوجتان؛
  4. تُكتب علامات الاقتباس المزدوجة داخل الحقل كعلامتي اقتباس مزدوجتين ""؛
  5. السطر الأول يجوز أن يكون ترويسة؛
  6. لا يحدد ترميز الأحرف، ولا الأنواع.
id,name,remark
1,张三,"喜欢 JSON,讨厌尾逗号"
2,李四,"他说:""这行有引号"""
3,王五,"多行
备注"

انتبه إلى القاعدة 3: الحقول التي تحتوي أسطرًا جديدة قانونية، لذا فالمحلل الساذج الذي يقسّم على \n ثم على , سيخطئ حتمًا.

2. JSON → CSV: قرر البنية أولًا

لا توجد سوى ثلاث استراتيجيات لتسطيح بنية شجرية إلى جدول ثنائي الأبعاد:

الاستراتيجية 1: تسطيح الحقول (الأكثر شيوعًا)

{ "id": 1, "user": { "name": "张三", "city": "杭州" }, "tags": ["a", "b"] }
id,user.name,user.city,tags
1,张三,杭州,"[""a"",""b""]"
  • اربط مفاتيح الكائنات المتداخلة بـ .؛
  • لا يمكن التعبير عن المصفوفات كأعمدة، فسلسلها بـ JSON.stringify مباشرةً في الخلية (وهي ما تزال حقل CSV قانوني)؛
  • العيب: لا تستطيع أدوات الجداول تصفية المصفوفة، فيلزم تحليل ثانٍ.

الاستراتيجية 2: توسيع المصفوفة إلى صفوف متعددة

id,user.name,tag
1,张三,a
1,张三,b

يناسب هذا علاقات واحد-إلى-متعدد حيث يجمع المستهلكون اللاحقون لكل عنصر. والعيب تكرار حقول الأب وتضخم عدد الصفوف.

الاستراتيجية 3: التقسيم إلى جداول متعددة

جدول رئيسي بالإضافة إلى جداول مرتبطة تُوصل بواسطة id — أنظف نهج، لكنه يتجاوز "تصديرًا واحدًا" ويشبه نمذجة بيانات أكثر.

أيها تستخدم؟ استخدم الاستراتيجية 1 للتصدير الموجَّه للبشر، والاستراتيجية 3 للتحميل إلى قاعدة بيانات، والاستراتيجية 2 لتدوير البيانات.

3. CSV → JSON: كيف تُخمَّن الأنواع

لا أنواع في CSV، فكل خطوة استعادة تخمين:

محتوى الخلية المعنى المحتمل خطر التحويل المباشر إلى عدد
00123 معرّف موظف، رمز بريدي فقدان الأصفار البادئة → 123
1.10 رقم إصدار، مبلغ يصبح 1.1
1e5 معرّف أو ترميز علمي يصبح 100000
2026-10-30 تاريخ أو نص غموض في المنطقة الزمنية والصيغة
13800138000 رقم هاتف ما يزال آمنًا عند 11 رقمًا، لكن بعض المعرّفات تفقد الدقة فعلًا
"" نص فارغ أو null غموض دلالي

الافتراضي المقترح: عامل كل شيء كنص، ودع مخططًا أو تعريف أعمدة صريحًا يقرر أي الأعمدة تصبح أعدادًا أو قيمًا منطقية. "الاستنتاج الذكي" ممتع عند التصدير ومصدر حوادث عند الاستيراد.

type ColumnType = 'string' | 'number' | 'boolean' | 'json';

/**
 * Converts CSV rows into objects according to the column definitions.
 * @param rows The raw row array; the first row is the header
 * @param columnTypes Mapping from column name to type
 * @returns The converted array of objects
 */
function rowsToRecords(
  rows: string[][],
  columnTypes: Record<string, ColumnType>,
): Array<Record<string, unknown>> {
  const [headerRow, ...dataRows] = rows;

  return dataRows.map((dataRow) => {
    const record: Record<string, unknown> = {};

    headerRow.forEach((columnName, columnIndex) => {
      const rawValue = dataRow[columnIndex] ?? '';
      const columnType = columnTypes[columnName] ?? 'string';

      if (columnType === 'number') {
        const numericValue = Number(rawValue);
        record[columnName] = Number.isFinite(numericValue) ? numericValue : null;
        return;
      }
      if (columnType === 'boolean') {
        record[columnName] = rawValue === 'true' || rawValue === '1';
        return;
      }
      if (columnType === 'json') {
        try {
          record[columnName] = JSON.parse(rawValue);
        } catch {
          record[columnName] = rawValue;
        }
        return;
      }
      record[columnName] = rawValue;
    });

    return record;
  });
}

4. ثلاثة مزالق حول Excel

4.1 الترميز

عندما يفتح Excel ملف CSV بترميز UTF-8 دون BOM، يتحول النص الصيني إلى نص مشوّه. وإضافة BOM (\uFEFF) عند التصدير هي أرخص إصلاح للتوافق:

const bom = '\uFEFF';
const csvContent = `${bom}${headerLine}\n${bodyLines.join('\n')}`;

4.2 الأعداد الطويلة تتحول إلى ترميز علمي

رقم هوية وطني بـ 18 رقمًا أو رقم طلب بـ 19 رقمًا يظهر في Excel بالشكل 1.23457E+17، والقيمة نفسها قُطعت بالفعل. وإضافة tab في بداية الحقل أو تغليفه كصيغة (="1234...") تحافظ على تنسيق النص، لكنها تحول الخلية إلى صيغة. والنهج الأسلم أن تخبر المستخدمين "استخدم ميزة الاستيراد وحدد العمود كنص"، أو أن تقدم ملف .xlsx إلى جانب التصدير.

4.3 الفواصل المتأثرة بالإعدادات المحلية

تفترض بعض الإعدادات الأوروبية الفاصلة المنقوطة ; فاصلًا افتراضيًا. وعند التوزيع عبر المناطق، إما أن تثبّت CSV على الفواصل وتعلن ذلك، أو تقدم نسخة TSV (مفصولة بـ tab) — فـ TSV أكثر تسامحًا مع الإعدادات المحلية.

5. التعامل مع الملفات الكبيرة

لا تحمّل ملفات CSV التي تتجاوز مئة ألف صف في الذاكرة دفعة واحدة:

import { createReadStream } from 'node:fs';
import { parse } from 'csv-parse';

const parser = createReadStream('orders.csv').pipe(
  parse({ columns: true, skipEmptyLines: true, bom: true }),
);

for await (const record of parser) {
  // Process one record at a time; memory usage is independent of file size
}

واتجاه التصدير يعمل بالطريقة نفسها: ولّد واكتب إلى تدفق أثناء العمل، بدلًا من لصق نص واحد عملاق.

6. فحص ذاتي لاتساق الرحلة ذهابًا وعودة

رحلة CSV (JSON → CSV → JSON) لا تكون متطابقة تمامًا تقريبًا أبدًا، لكن يمكنك تحديد الخسائر المقبولة:

الفحص التوقع
عدد الحقول متطابق
قيم النصوص متطابقة (بما في ذلك الأصفار البادئة)
الأنواع الرقمية تحددها تعريف الأعمدة لا الاستنتاج
الكائنات المتداخلة قابلة للاستعادة عبر المسارات المنقطة
المصفوفات مرئية بعد التسلسل إلى نص JSON
القيم الفارغة تُطبَّع إلى null أو النص الفارغ — اختر واحدًا بالضبط

ووضع هذا الجدول في الاختبارات أكثر فاعلية بكثير من اتفاق شفهي.

7. قائمة تحقق

  • [ ] دلالات "الصف الواحد سجل واحد" معلنة صراحةً ومنعكسة في اسم ملف التصدير؛
  • [ ] كل عمود قد يفقد الدقة يُصدَّر كنص؛
  • [ ] ملفات CSV العربية تتضمن BOM، أو يُعلن الترميز صراحةً؛
  • [ ] الحقول التي تحتوي فواصل أو علامات اقتباس أو أسطرًا جديدة مهرَّبة بشكل صحيح (استخدم مكتبة، ولا تلصق نصوصًا بنفسك)؛
  • [ ] الملفات الكبيرة تمر بقراءات وكتابات متدفقة؛
  • [ ] الاستيراد يحوّل الأنواع حسب تعريف الأعمدة، لا عبر استنتاج ذكي شامل.

FAQ

كيف أختار بين التسطيح وتوسيع الصفوف وتقسيم الجداول؟

سطّح للتصدير الموجَّه للبشر: اربط المفاتيح المتداخلة بـ . وسلّس المصفوفات بـ JSON.stringify في الخلية، بتكلفة عدم إمكانية تصفية تلك المصفوفات في جدول بيانات. وقسّم إلى جدول أب وجداول مرتبطة عندما يكون الهدف قاعدة بيانات. ووسّع المصفوفات إلى صفوف إضافية عندما يجمع المستهلكون اللاحقون لكل عنصر، مع قبول تكرار حقول الأب.

لماذا لا ينبغي أن يستنتج استيراد CSV الأنواع تلقائيًا؟

تحدد RFC 4180 الفواصل والهروب لكن ليس الأنواع، فكل خطوة استعادة تخمين: يفقد 00123 أصفاره البادئة، ويصبح 1.10 هو 1.1، و1e5 هو 100000. عامل كل شيء كنص افتراضيًا ودع مخططًا أو تعريف أعمدة صريحًا يقرر أي الأعمدة تصبح أعدادًا أو قيمًا منطقية.

هل أتوقع أن تعيد رحلة CSV إنتاج JSON الأصلي تمامًا؟

لا تقريبًا أبدًا. حدد الخسائر المقبولة بدلًا من ذلك: عدد حقول متطابق، وقيم نصوص متطابقة بما فيها الأصفار البادئة، وأنواع رقمية يقررها تعريف الأعمدة، وكائنات متداخلة قابلة للاستعادة عبر المسارات المنقطة، ومصفوفات مرئية بعد التسلسل، وقيم فارغة مطبَّعة إلى null أو النص الفارغ — اختر واحدًا بالضبط.