JSON في JavaScript: نصّ وليس كائنًا
صيغة JSON (اختصارًا لـ JavaScript Object Notation) هي صيغة نصية لتبادل البيانات. قد يبدو شكلها مطابقًا للكائن في JavaScript، لكنها في الحقيقة مجرّد سلسلة نصية (string) من المحارف، يمكنك إرسالها عبر الشبكة، أو حفظها في ملف، أو لصقها داخل ملف إعدادات.
// كائن JavaScript — قيمة حيّة في الذاكرة.
const user = { name: "Rosa", age: 30 };
// JSON — سلسلة نصية تمثّل نفس البيانات.
const json = '{"name":"Rosa","age":30}';
من السهل أن تختلط هذه المفاهيم على البعض لأنها متشابهة في الشكل. إليك الصورة الذهنية التي تضعك على الطريق الصحيح: الكائنات تعيش داخل برنامجك، أما JSON فهو شكلها عندما تخرج إلى العالم الخارجي. وللتنقّل بين الاثنين لديك دالتان: JSON.stringify (من كائن إلى نص) وJSON.parse (من نص إلى كائن).
JSON.stringify: تحويل كائن إلى JSON
حوّل أي قيمة في JavaScript إلى صيغتها بتنسيق JSON:
النتيجة هي سلسلة نصية من سطر واحد بدون أي مسافات — مضغوطة ومثالية للإرسال عبر الشبكة. وفحص typeof يؤكّد ذلك: string وليس object.
ولتحسين قراءة النتيجة أثناء تتبّع الأخطاء، مرّر وسيطين إضافيين. الوسيط الثاني هو replacer (سنأتي إليه لاحقًا)، وتمرير null يعني "ضمّن كل شيء". أما الوسيط الثالث فهو مقدار المسافة البادئة:
هذا هو الشكل المنسَّق المعتاد. استخدم 2 أو 4 مسافات، فهذا ما تُخرجه معظم الأدوات.
JSON.parse: من نص إلى كائن
العملية العكسية: تمرّر نص JSON فتستعيد قيمة JavaScript جاهزة للاستخدام.
بمجرد ما تحلّل النص، بتتعامل مع كائن عادي تمامًا — وصول بالنقطة، وصول بالأقواس المربعة، دوال المصفوفات، كل شيء متاح.
لكن JSON.parse صارم جدًا. كل الحالات التالية بترمي SyntaxError:
JSON.parse("{name: 'Rosa'}"); // مفتاح بدون علامات اقتباس، علامات اقتباس مفردة
JSON.parse('{"name": "Rosa",}'); // فاصلة زائدة في النهاية
JSON.parse("// a comment\n{}"); // التعليقات غير مسموح بها
JSON.parse(""); // سلسلة فارغة
أي بيانات تصلك من خارج برنامجك — سواء جاءت من استجابة fetch، أو من ملف، أو من إدخال المستخدم — لفّ عملية التحليل داخل try/catch:
قيم لا تنجو من رحلة الذهاب والعودة
صيغة JSON في JavaScript تدعم ستة أنواع فقط من القيم: النصوص، والأرقام، والقيم المنطقية، وnull، والمصفوفات، والكائنات العادية. أيّ شيء خارج هذه القائمة إمّا يُحذف، أو يتحوّل إلى شكل آخر، أو يتسبّب في خطأ.
ما الذي يحدث فعليًا:
- الدوال (Functions) وقيمة
undefinedتُحذف بصمت من الكائنات. أمّا داخل المصفوفات فتتحوّل إلىnull، لأن المصفوفات في JSON لا تحتمل فجوات. - كائنات
Dateتُحوَّل إلى سلسلة نصية بصيغة ISO عبر ميثودtoJSONالخاصة بها. وعند إعادة التحليل ستحصل على نص عادي، لا على كائنDate. BigIntيُطلق خطأTypeError، لأنه ببساطة لا يوجد مقابل له في أرقام JSON.Mapو**Set** والمراجع الدائرية (circular references) لا تعمل مباشرةً هي الأخرى.
لاستعادة كائنات Date كما كانت بعد رحلة الذهاب والعودة، نستعين بدالة الـ_reviver_ في JSON.parse:
الدالة reviver تُستدعى لكل زوج مفتاح/قيمة، وتتيح لك تعديل القيم أثناء قراءتها.
الـ Replacer: التحكم في ما يتم تحويله إلى JSON
المعامل الثاني لدالة JSON.stringify يمنحك تحكمًا كاملًا فيما يظهر في الناتج النهائي. مرّر مصفوفة تحتوي على أسماء المفاتيح التي تريد الإبقاء عليها فقط:
أو يمكنك تمرير دالة لتطبيق أي منطق تريده — حذف حقول، إخفاء قيم، أو تحويلها أثناء التسلسل:
إرجاع undefined يؤدي إلى حذف المفتاح، أما إرجاع أي قيمة أخرى فيستبدلها.
التحكم في التسلسل عبر toJSON
إذا كان الكائن يحتوي على دالة toJSON، فإن JSON.stringify تستدعيها وتُسلسِل القيمة المُرجَعة بدلًا من الكائن نفسه. بهذه الطريقة يتحكّم Date في صيغته الخاصة، ويمكنك الاستفادة من نفس الآلية في كائناتك:
ممتاز للـ classes اللي تحتاج شكلاً خارجياً ثابتاً بغضّ النظر عمّن يقوم بعملية التحويل (serialization).
النسخ العميق (الحيلة القديمة، والطريقة الأفضل)
لسنوات طويلة، كان السطر JSON.parse(JSON.stringify(obj)) هو الحل السحري لعمل نسخة عميقة (deep clone) من أي كائن عادي:
تعمل هذه الطريقة طالما أن الكائن يحتوي فقط على قيم متوافقة مع JSON. أما التواريخ و Map والدوال فستُفسد النتيجة (راجع مشاكل الذهاب والإياب التي ذكرناها سابقًا).
توفّر إصدارات JavaScript الحديثة الدالة structuredClone، وهي تتعامل مع التواريخ و Map و Set والمصفوفات المُكتَبة (typed arrays)، بل وحتى المراجع الدائرية:
استخدم structuredClone متى أمكنك ذلك، واحتفظ بحيلة JSON.parse(JSON.stringify(...)) كحلّ سريع لنسخ البيانات البسيطة عند الحاجة.
مثال واقعي: جلب البيانات وتحليل JSON
أكثر مكان ستتعامل فيه مع JSON هو طلبات HTTP. لاحظ أن fetch لا يُحلّل JSON تلقائيًا، بل عليك استدعاء .json() على الاستجابة (وهي في الحقيقة JSON.parse تُطبَّق على جسم الاستجابة):
إرسال JSON هو العملية العكسية: نستخدم JSON.stringify لتحويل الجسم إلى نص، ثم نضبط ترويسة Content-Type.
await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Rosa", age: 30 }),
});
هاتان الحالتان تُغطّيان معظم ما ستتعامل معه مع JSON في المشاريع الحقيقية.
الخطوة التالية: الوصول الاختياري (Optional Chaining)
بعد تحويل JSON إلى كائن، كثيرًا ما نجد حقولًا قد تكون موجودة وقد لا تكون — مثل user.address.city الذي ربما لا يوجد أصلًا، أو response.data.items الذي قد يكون مفقودًا من الاستجابة. للوصول إلى خصائص متداخلة بعمق دون أن ينهار الكود، نستخدم الوصول الاختياري (?.)، وهذا ما سنشرحه في الصفحة التالية.
الأسئلة الشائعة
ما الفرق بين JSON وكائن JavaScript؟
JSON هو صيغة نصية — أي مجرّد نص يشبه شكل كائن JavaScript لكن بقواعد أكثر صرامة. المفاتيح يجب أن تكون بين علامتي اقتباس مزدوجتين، والنصوص كذلك، والقيم المسموح بها محصورة في: النصوص، الأرقام، القيم المنطقية، null، المصفوفات، والكائنات العادية. أمّا كائن JavaScript فهو قيمة حيّة في الذاكرة يمكن أن يحتوي على دوال، أو undefined، أو كائنات Date، أو أي شيء آخر.
كيف أحوّل كائن JavaScript إلى JSON؟
استخدم JSON.stringify(obj)، فهو يمرّ على الكائن ويُرجع نص JSON. ولو أردت إخراجًا منسّقًا ومقروءًا، مرّر وسيطًا ثالثًا مثل JSON.stringify(obj, null, 2) لإضافة مسافتين كإزاحة. انتبه: الدوال وقيم undefined تُحذف من الكائنات، وتتحوّل إلى null داخل المصفوفات.
لماذا يُطلق JSON.parse خطأً؟
لأن JSON.parse صارم جدًا: الفواصل الزائدة في النهاية، وعلامات الاقتباس المفردة، والمفاتيح بدون اقتباس، والتعليقات — كلّها تُسبّب SyntaxError. لذلك ضع الاستدعاء داخل try/catch في أي مكان يأتي فيه النص من طلب شبكة أو ملف أو إدخال مستخدم، أي أي مصدر قد لا يكون JSON صالحًا.
هل يحافظ JSON.stringify على كائنات Date؟
لا. كائن Date يتحوّل إلى نص بصيغة ISO مثل "2026-01-15T10:30:00.000Z". وعند إعادته عبر JSON.parse ستحصل على نص عادي، وليس على كائن Date. إن احتجت استرجاعه ككائن Date فعلًا، استخدم وسيط reviver في JSON.parse لتحويل نصوص ISO إلى كائنات Date.