ملف JSON كبير لا يفتح؟ اختر الأداة المناسبة حسب الحجم
الخلاصة: تصديرات JSON بحجم عشرات الميغابايتات تجمّد محررات النصوص والأدوات عبر الإنترنت غالبًا، بينما لا يستغرق التحليل نفسه سوى بضع مئات من أجزاء الثانية. قمنا بقياس الوقت والذاكرة في كل مرحلة لعينة بحجم 45 ميغابايت تحتوي على 500 ألف سجل، وأظهرنا أن التكلفة الحقيقية تكمن في عدد الأسطر والعُقد، ثم بنينا جدولًا لاختيار الأداة حسب حجم الملف: عارض عبر الإنترنت، سطر الأوامر، المعالجة التدفقية، وتطبيق سطح المكتب.

ملف JSON بحجم 45 ميغابايت يُحلَّل في 249 جزءًا من الألف من الثانية. النصف دقيقة التي تنتظرها تأتي من عرض خمسة ملايين سطر من النص و3.5 مليون عقدة شجرية. إذا خلطت بين الاثنين، فستُلام الطرف الخطأ في كل مرة.
أولًا: قِس أولًا — التحليل ليس هو الجاني
نقرتان مزدوجتان على تصدير .json بحجم عشرات الميغابايتات، يدور المحرر ثلاثين ثانية، فينطلق الانعكاس: "تحليل JSON بطيء". القياس يقول العكس تمامًا. على جهازنا (Node 22)، أنشأنا عينة من 500 ألف سجل بخمسة حقول لكل منها (id، email، active، score، tags)، بحجم 45 ميغابايت بالصيغة المضغوطة:
| العملية | النتيجة المقاسة |
|---|---|
JSON.parse كاملة |
249 مللي ثانية |
JSON.stringify(data, null, 2) — مخرجات منسّقة |
215 مللي ثانية |
| النص المنسّق | 71 ميغابايت، 5,000,001 سطر |
| إجمالي عُقد الشجرة (سجلات + حقول) | حوالي 3,500,001 |
| ذاكرة RSS لعملية Node بعد التحليل | 313 ميغابايت (حوالي 7× حجم الملف) |
ينتهي التحليل في ربع ثانية. كان المحلل قد أنهى عمله قبل أن ترفع إصبعك عن زر الفأرة. كل الثواني التالية تُصرف في طبقة "عرض النتيجة للإنسان": محرر النصوص يجب أن يرتّب خمسة ملايين سطر، ويحسب تلوين الصياغة، ويحافظ على مخزن التراجع؛ والعارض الشجري يجب أن ينشئ عنصر DOM لكل عقدة من 3.5 مليون عقدة — والمتصفح يتعثر عند حوالي مئة ألف عقدة، ويفقد الاستجابة قبل المليون بكثير.
الاستنتاج يشير إلى مكان مختلف تمامًا: توقف عن محاربة العرض — استخدم أدوات لا تعرض كل شيء دفعة واحدة.
ثانيًا: لماذا يتجمّد — عدد الأسطر والعُقد هو التكلفة الحقيقية
حجم الملف ليس سوى السطح. رقمان مشتقّان هما ما يحطّم الأدوات:
- عدد الأسطر. ملف JSON مضغوط بحجم 45 ميغابايت يتحول إلى خمسة ملايين سطر عند تنسيقه. أيًّا كان الجزء الذي تفتحه، على المحرر أن يبني بنية أسطر الملف بأكمله أولًا، ويحلّل حالات التلوين، ويخصص المخازن — التكلفة تتناسب مع إجمالي الأسطر.
- عدد العُقد. نصف مليون سجل × (كائن واحد + خمسة حقول) يعني 3.5 مليون عقدة. أي عارض يبني الشجرة كاملة قبل العرض يدفع الذاكرة والوقت نفسيهما — سواء فتحت فرعًا واحدًا أو كلها.
من هنا تنبع نتيجتان تكذبان الحدس. التنسيق قبل المعاينة يضر — يتضخم الملف من 45 إلى 71 ميغابايت ويتضاعف عدد الأسطر أربعين مرة، عبء صافٍ إضافي على الأداة. دفع الملف عبر cat إلى الطرفية يضر أيضًا — مخزن التمرير يصبح المشكلة الجديدة؛ لأول بايتات، يكفي head -c 2000.
ثالثًا: اختر الأداة حسب الحجم — جدول القرار
الأحجام المختلفة تفرض أدوات مختلفة تمامًا. بدمج القياسات أعلاه مع الأدلة المنشورة (OpenFormatter تضع منطقة الراحة للأدوات عبر الإنترنت عند 2-5 ميغابايت؛ PureJSON تقدّر الذاكرة بعد تحليل مستند بحجم 500 ميغابايت بـ 2-4 جيجابايت)، يتشكل هذا الجدول:
| حجم الملف | الخيار الأول | لماذا |
|---|---|---|
| أقل من 2 ميغابايت | أي أداة عبر الإنترنت | وصول فوري؛ عارض شجري + تحقق بأسلوب /parse في خطوة واحدة |
| 2 – 20 ميغابايت | محرر محلي (VS Code) أو أداة محلية في المتصفح | الأدوات عبر الإنترنت تبدأ في التراجع؛ المعالجة المحلية لا ترسل شيئًا |
| 20 – 100 ميغابايت | استكشاف jq عبر سطر الأوامر + عارض شجري افتراضي | المحرر يبدأ في التعثر؛ jq يستخرج أي مقطع في ثوانٍ |
| 100 ميغابايت – 1 جيجابايت | jq --stream / ijson، معالجة تدفقية |
ذاكرة التحليل التقليدي تتضخم إلى ~7× حجم الملف؛ لا تتسع — استخدم التدفق |
| أكثر من 1 جيجابايت أو نمو مستمر | غيّر الصيغة: NDJSON / CSV / Parquet | "العملاق-المصفوفة" يجب تحليله بالكامل قبل أن يفيد أول سجل؛ المشكلة في الصيغة نفسها |
أداة /parse في هذا الموقع تغطي الدرجة الأولى: العارض الشجري والتحقق. الملفات الأكبر يجب أن تتجه إلى تطبيق سطح المكتب، حتى لا تُلصق عشرات الميغابايتات في صفحة ويب أبدًا.
رابعًا: لا تفتح الملف — اسأل عن بنيته أولًا
أمام ملف JSON كبير غريب، الخطوة الأولى ليست الفتح، بل تخصيص ثوانٍ قليلة لفهم شكله. يجيب jq على ثلاثة أسئلة في حوالي 200 مللي ثانية:
# ماذا يوجد في الأعلى: مصفوفة أم كائن؟ كم سجلًا؟
jq 'if type == "array" then {type: "array", count: length} else {type: "object", keys: (keys|length)} end' huge.json
# كيف يبدو السجل الأول (واحد فقط، دون طوفان)؟
jq '.[0]' huge.json | head -40
# كل أسماء الحقول (من السجل الأول)
jq '.[0] | keys' huge.json
بعد معرفة البنية، تنحسر معظم الطلبات إلى "استخراج حقل واحد" — بل لا يلزم فتح الملف أصلًا. مثلًا، للحصول على بريد كل مستخدم:
jq -r '.[].email' huge.json | less
# مع مرشح فوقه
jq -r '.[] | select(.active == true) | .email' huge.json
القاعدة بسيطة: لـ"المشاهدة"، عارض افتراضي؛ لـ"البحث والاستخراج"، jq. لا تخلط الدورين في أداة واحدة.
خامسًا: هاوية الذاكرة — الوضع العادي مقابل التدفق، فرق 150×
على العينة نفسها (45 ميغابايت)، قاس /usr/bin/time -v ذاكرة jq القصوى في وضعين:
| الوضع | الأمر | الذاكرة القصوى (RSS) | الوقت |
|---|---|---|---|
| عادي (تحميل كامل) | jq '.[0].email' sample.json |
542 ميغابايت | 0.7 ثانية |
| تدفق | jq --stream 'select(.[0][1]=="email") | .[1]' sample.json |
3.6 ميغابايت | 2.5 ثانية |
الوضع العادي يقرأ الملف كاملًا ويبني مخطط الكائنات؛ القمة البالغة 542 ميغابايت تؤكد قاعدة "مخطط الكائنات ≈ 7-12× حجم النص". أما --stream فيستهلك المدخلات رمزًا برمز، ويحافظ على الذاكرة عند رقم من خانة واحدة بالميغابايت — مقابل وقت إجمالي أطول. عند تجاوز حد الجيجابايت، هذا الفرق هو الذي يفصل بين "يعمل" و"يُقتل بواسطة OOM killer":
# للملفات الأكبر من الذاكرة: --stream هو الوضع الوحيد الذي لا يحمل كل شيء أبدًا
jq --stream -n 'fromstream(1|truncate_stream(inputs))' huge.json
المكافئ في بايثون هو ijson: تُنتج ijson.items(f, 'item') عناصر المصفوفة واحدًا تلو الآخر بذاكرة ثابتة هي نفسها. الثمن المشترك لهذه الأدوات هو التخلي عن الوصول العشوائي — التدفق يتقدم للأمام فقط، والمرور الثاني يعني إعادة قراءة الملف.
سادسًا: إلى أين يصل المتصفح — حدود المعالجة المحلية
تظل الأدوات عبر الإنترنت قابلة للاستخدام، ضمن حدودها. تملك تبويبة المتصفح الحديثة حوالي 1-4 جيجابايت من الذاكرة؛ ملف بحجم 45 ميغابايت يُحلَّل إلى مخطط زائد شجرة ينجو عادةً. بضع مئات من الميغابايتات على حافة الهاوية فعلًا. عند اختيار عارض عبر الإنترنت، اطلب ثلاثة أشياء:
- المعالجة تجري محليًا (المُلصق لا يُرسل إلى الخادم أبدًا) — يحمي البيانات ويوفر وقت الرفع؛ كل الأدوات في هذا الموقع تعمل بالكامل داخل المتصفح وتعمل دون اتصال؛
- التحليل يعمل في Web Worker — الخيط الرئيسي لا يتجمد، والانسداد يكلّف تبويبة واحدة فقط، لا المتصفح كله؛
- شجرة افتراضية أو كسبة — الفروع المطوية لا تكلّف تقريبًا شيئًا؛ لا يُعرض إلا المفتوح.
في هذا الموقع: /parse مناسب لاستكشاف البنية وتحديد الأخطاء في الملفات حتى بضع عشرات من الميغابايتات؛ وفوق ذلك، jq أو تطبيق سطح المكتب، ويُحجز المتصفح للمقاطع الصغيرة.
سابعًا: الصيغة الصحيحة تلغي نصف المشكلة
إذا كنت تتحكم في كيفية توليد الملف، فالعارض المحسّن هو الدواء الخطأ — امنعه من النمو ليصبح "عملاق-مصفوفة":
- NDJSON/JSONL: سجل واحد لكل سطر. الإضافة لا تعيد كتابة الملف، و
headوgrepوtailوsplitتعمل مباشرة؛ الملف المقطوع يفقد السطر الأخير فقط. يحتوي دليل JSONL لدينا على الإجراء الكامل للتعامل مع الأسطر التالفة. - تصدير مقسّم إلى صفحات: واجهة API تعيد 100 ألف سجل ينبغي أن تقبل معامل صفحة — 100 دفعة واحدة ألطف للطرفين.
- البيانات الجدولية ليست لـ JSON: سجل واحد لكل سطر، حقول ثابتة دون تداخل — هذا جدول. CSV أو Parquet أصغر حجمًا وبيئتاهما أغنى.
رقم يستحق الحفظ: اقتسم مصفوفة من 500 ألف سجل إلى نصفين، فالنصف الأول يبقى ملف NDJSON سليمًا (نصف السجلات ينجو)، بينما العملاق-المصفوفة المكافئ يضيع بأكمله. خمسمئة سجل يُنقذون من 501 أفضل من الصفر.
ثامنًا: قائمة التحقق
- استكشف البنية أولًا بـ
jq '.[0] | keys'أو عينة من 500 سجل؛ - لا تنسّق أبدًا لمجرد فتح الملف — مضاعفة الأسطر تبطئه فقط؛
- أقل من 2 ميغابايت: عارض شجري عبر الإنترنت؛ فوق 20 ميغابايت: سطر الأوامر؛ فوق 1 جيجابايت: دائمًا
--streamأو ijson؛ - تأكد من أن الأداة عبر الإنترنت تعالج محليًا (المُلصق لا يخرج من المتصفح)؛
- إن كنت تتحكم في المُولِّد، فانتقل إلى NDJSON أو التقسيم إلى صفحات واسقط العملاق-المصفوفة من المصدر.
الأسئلة الشائعة
لماذا يستغرق المحرر عشرات الثواني لفتح JSON بينما البرنامج يحلله في 200 مللي ثانية؟
التحليل هو كود أصيل يحول النص إلى بنية بيانات: 249 مللي ثانية لعينتنا البالغة 45 ميغابايت. يتعثر المحرر في العرض: خمسة ملايين سطر لترتيبها وتلوينها وحفظها كمخزن تراجع؛ والشجرة تبني DOM لـ 3.5 مليون عقدة. العارض الافتراضي أو الاستخراج المباشر عبر jq يتجاوزان كل ذلك.
هل تنفد ذاكرة jq مع JSON أكبر من 1 جيجابايت؟
في الوضع العادي، نعم: يحمّل المستند كاملًا، ويصل مخطط الكائنات إلى 7-12× حجم الملف. مع --stream، تُستهلك المدخلات رمزًا برمز: وصلت اختبارنا البالغة 45 ميغابايت إلى قمة 3.6 ميغابايت، وتبقى الذاكرة مستوية أيًّا كان حجم الملف — مقابل مرور تسلسلي واحد فقط.
ما حجم الملف الذي تستطيع أدوات JSON عبر الإنترنت التعامل معه؟
معظمها مرتاحة حتى 2-5 ميغابايت وترتد بوضوح فوق 10 ميغابايت. الحد الصارم هو ذاكرة التبويبة (1-4 جيجابايت)، لكن عند بضع عشرات من الميغابايتات يصبح انتظار التحليل وبناء الشجرة مؤلمًا بالفعل. فوق 20 ميغابايت، استخدم أدوات محلية أو سطر الأوامر.
كيف أعرض قيمة حقل واحد في JSON ضخم؟
استخرج عبر jq دون فتح الملف: jq -r '.[].email' huge.json يطبع كل العناوين؛ ومع مرشح — jq -r '.[] | select(.active == true) | .email'. للسجلات الأولى: jq '.[0:5]' huge.json — أسرع بمراتب قدرة من فتح الملف.
هل التنسيق قبل الفتح يساعد؟
لا. التنسيق يحوّل 45 ميغابايت مضغوطة إلى 71 ميغابايت وسطرًا واحدًا إلى خمسة ملايين، مضيفًا عبئًا صافيًا على المحرر والعارض. احتفظ بالتنسيق لما سيقرؤه الإنسان — وفقط إذا كان الملف صغيرًا بما يكفي.