JSON: التحليل والتحويل النصي
ما هو JSON؟
JSON هو اختصار لـ JavaScript Object Notation (ترميز كائنات JavaScript). وهو تنسيق خفيف قائم على النص لتبادل البيانات، سهل القراءة والكتابة للبشر وسهل التحليل والتوليد للأجهزة. على الرغم من احتوائه على كلمة "JavaScript" في اسمه، إلا أن JSON هو تنسيق مستقل عن اللغة يُستخدم عبر كل لغة برمجة ومنصة تقريبًا. أصبح المعيار الفعلي لتبادل البيانات على الويب، حيث يُشغّل واجهات REST APIs وملفات التكوين وتخزين البيانات والاتصال بين العملاء والخوادم.
اشتُق JSON من مجموعة فرعية من صيغة JavaScript، وستبدو بنيته مألوفة فورًا لأي مطور JavaScript. ومع ذلك، فإن لـ JSON قواعد أكثر صرامة من كائنات JavaScript. فهم هذه القواعد، إلى جانب دالتي JSON.parse() و JSON.stringify() المدمجتين، أمر ضروري لكل مطور يعمل مع واجهات الويب أو التخزين المحلي أو ملفات التكوين أو أي شكل من أشكال تبادل البيانات المنظمة.
قواعد تنسيق JSON
يتبع JSON مجموعة صارمة من قواعد التنسيق التي تميزه عن صيغة كائنات JavaScript. مخالفة أي من هذه القواعد ستؤدي إلى فشل التحليل. فهم هذه القواعد بدقة سيوفر عليك ساعات من تتبع أخطاء التحليل الغامضة.
مثال: JSON مقابل صيغة كائنات JavaScript
// كائن JavaScript صالح -- لكنه ليس JSON صالحًا
const jsObject = {
name: 'John', // مفاتيح غير مقتبسة -- غير صالح في JSON
age: 30,
'is-active': true,
greet() { // الدوال -- غير مسموح بها في JSON
return 'hello';
},
hobby: undefined, // undefined -- غير مسموح به في JSON
createdAt: new Date(), // كائنات Date -- غير مسموح بها في JSON
};
// سلسلة JSON صالحة -- لاحظ الاختلافات
const jsonString = `{
"name": "John",
"age": 30,
"isActive": true,
"hobbies": ["reading", "coding"],
"address": {
"city": "New York",
"country": "USA"
},
"spouse": null
}`;
// ملخص قواعد JSON:
// 1. المفاتيح يجب أن تكون سلاسل نصية بعلامات اقتباس مزدوجة
// 2. السلاسل النصية يجب أن تستخدم علامات اقتباس مزدوجة (ليس مفردة)
// 3. لا يُسمح بالفواصل الزائدة
// 4. لا يُسمح بالتعليقات
// 5. الأنواع المدعومة: string، number، boolean، null، object، array
// 6. غير مدعومة: undefined، الدوال، Date، RegExp، Symbol، Infinity، NaN
مثال: أنواع البيانات الصالحة في JSON
// سلسلة نصية -- يجب استخدام علامات اقتباس مزدوجة
"Hello, World!"
// رقم -- صحيح أو عشري، بدون أصفار بادئة
42
3.14
-17
2.5e10
// قيمة منطقية
true
false
// قيمة فارغة
null
// كائن -- مجموعة مرتبة من أزواج المفتاح والقيمة
{"key": "value", "count": 5}
// مصفوفة -- قائمة مرتبة من القيم
[1, "two", true, null, {"nested": "object"}]
// بنية متداخلة معقدة
{
"users": [
{
"id": 1,
"name": "Alice",
"scores": [95, 87, 92],
"active": true
},
{
"id": 2,
"name": "Bob",
"scores": [88, 91, 76],
"active": false
}
],
"total": 2,
"page": 1
}
JSON.parse() -- تحويل سلاسل JSON إلى قيم JavaScript
تأخذ دالة JSON.parse() سلسلة نصية بتنسيق JSON وتحولها إلى قيمة JavaScript. هذه هي الطريقة الأساسية لاستهلاك البيانات المستلمة من واجهات APIs أو المقروءة من الملفات أو المسترجعة من التخزين المحلي. تأخذ الدالة معاملين: سلسلة JSON المراد تحليلها ودالة إحياء اختيارية يمكنها تحويل القيم أثناء التحليل.
مثال: الاستخدام الأساسي لـ JSON.parse()
// تحليل كائن بسيط
const jsonStr = '{"name": "Alice", "age": 30, "isStudent": false}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // "Alice"
console.log(obj.age); // 30
console.log(obj.isStudent); // false
console.log(typeof obj); // "object"
// تحليل مصفوفة
const arrStr = '[1, 2, 3, "four", true, null]';
const arr = JSON.parse(arrStr);
console.log(arr); // [1, 2, 3, "four", true, null]
console.log(arr.length); // 6
// تحليل القيم البدائية
console.log(JSON.parse('42')); // 42
console.log(JSON.parse('"hello"')); // "hello"
console.log(JSON.parse('true')); // true
console.log(JSON.parse('null')); // null
// تحليل البيانات المتداخلة
const nested = '{"user": {"name": "Bob", "tags": ["admin", "editor"]}}';
const data = JSON.parse(nested);
console.log(data.user.name); // "Bob"
console.log(data.user.tags[0]); // "admin"
مثال: دالة الإحياء (Reviver)
// دالة الإحياء تستقبل كل زوج مفتاح-قيمة أثناء التحليل
// يمكنها تحويل القيم قبل أن تصبح جزءًا من النتيجة
const jsonStr = '{"name": "Alice", "birthDate": "1995-06-15", "score": "85"}';
// بدون دالة إحياء
const raw = JSON.parse(jsonStr);
console.log(typeof raw.birthDate); // "string"
console.log(typeof raw.score); // "string"
// مع دالة إحياء -- تحويل التواريخ والأرقام
const parsed = JSON.parse(jsonStr, (key, value) => {
// تحويل سلاسل التاريخ إلى كائنات Date
if (key === 'birthDate') {
return new Date(value);
}
// تحويل السلاسل الرقمية إلى أرقام
if (key === 'score') {
return Number(value);
}
return value;
});
console.log(parsed.birthDate instanceof Date); // true
console.log(typeof parsed.score); // "number"
// دالة إحياء لتصفية الخصائص
const filtered = JSON.parse(
'{"name": "Alice", "password": "secret", "email": "alice@example.com"}',
(key, value) => {
// استبعاد الحقول الحساسة بإرجاع undefined
if (key === 'password') return undefined;
return value;
}
);
console.log(filtered); // {name: "Alice", email: "alice@example.com"}
// دالة الإحياء تعالج الكائنات المتداخلة من الأسفل إلى الأعلى
const nestedJson = '{"a": {"b": {"c": 1}}}';
JSON.parse(nestedJson, (key, value) => {
console.log(key, value);
return value;
});
// "c" 1
// "b" {c: 1}
// "a" {b: {c: 1}}
// "" {a: {b: {c: 1}}} (المفتاح الفارغ = الكائن الجذر)
معالجة أخطاء التحليل
عندما يواجه JSON.parse() صيغة JSON غير صالحة، يطرح خطأ SyntaxError. معالجة هذه الأخطاء بشكل صحيح أمر بالغ الأهمية في التطبيقات الإنتاجية، خاصة عند تحليل البيانات من مصادر خارجية مثل مدخلات المستخدم أو استجابات API أو الملفات. يجب دائمًا تغليف JSON.parse() في كتلة try-catch عند التعامل مع مدخلات غير موثوقة.
مثال: تحليل JSON قوي
// أخطاء JSON الشائعة وكيفية معالجتها
function safeParse(jsonString, fallback = null) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error('خطأ تحليل JSON:', error.message);
return fallback;
}
}
// JSON صالح
console.log(safeParse('{"name": "Alice"}'));
// {name: "Alice"}
// غير صالح: علامات اقتباس مفردة
console.log(safeParse("{'name': 'Alice'}"));
// خطأ تحليل JSON: Unexpected token...
// null
// غير صالح: فاصلة زائدة
console.log(safeParse('{"name": "Alice",}'));
// خطأ تحليل JSON: Unexpected token...
// null
// غير صالح: قيمة undefined
console.log(safeParse('{"name": undefined}'));
// خطأ تحليل JSON: Unexpected token...
// null
// سلسلة فارغة
console.log(safeParse(''));
// خطأ تحليل JSON: Unexpected end of JSON input
// null
// استخدام قيمة احتياطية
const config = safeParse(localStorage.getItem('settings'), {
theme: 'light',
language: 'en'
});
// تعيد الإعدادات الافتراضية إذا كانت القيمة المخزنة غير صالحة
// التحقق من بنية JSON بعد التحليل
function parseAndValidate(jsonStr, requiredKeys = []) {
try {
const data = JSON.parse(jsonStr);
for (const key of requiredKeys) {
if (!(key in data)) {
throw new Error(`مفتاح مطلوب مفقود: ${key}`);
}
}
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
const result = parseAndValidate(
'{"name": "Alice"}',
['name', 'email']
);
console.log(result);
// {success: false, error: "مفتاح مطلوب مفقود: email"}
eval() لتحليل JSON. بينما يمكن لـ eval() تحليل سلاسل JSON، فإنها تنفذ أيضًا كود JavaScript عشوائي، مما يخلق ثغرة أمنية خطيرة. استخدم دائمًا JSON.parse()، التي تحلل البيانات فقط ولا يمكنها تنفيذ الكود.JSON.stringify() -- تحويل قيم JavaScript إلى سلاسل JSON
تحوّل دالة JSON.stringify() قيمة JavaScript إلى سلسلة نصية بتنسيق JSON. هذه هي الطريقة التي تُسلسل بها البيانات لإرسالها إلى APIs أو تخزينها في التخزين المحلي أو حفظها في ملفات. تقبل الدالة ثلاثة معاملات: القيمة المراد تحويلها ودالة أو مصفوفة مستبدل اختيارية ومعامل المسافة الاختياري للتنسيق.
مثال: الاستخدام الأساسي لـ JSON.stringify()
// تحويل كائن بسيط إلى نص
const user = { name: 'Alice', age: 30, isStudent: false };
const jsonStr = JSON.stringify(user);
console.log(jsonStr);
// '{"name":"Alice","age":30,"isStudent":false}'
console.log(typeof jsonStr); // "string"
// تحويل مصفوفة إلى نص
const arr = [1, 'two', true, null];
console.log(JSON.stringify(arr));
// '[1,"two",true,null]'
// تحويل كائنات متداخلة إلى نص
const data = {
user: { name: 'Bob', tags: ['admin', 'editor'] },
timestamp: 1700000000
};
console.log(JSON.stringify(data));
// '{"user":{"name":"Bob","tags":["admin","editor"]},"timestamp":1700000000}'
// تحويل القيم البدائية إلى نص
console.log(JSON.stringify(42)); // "42"
console.log(JSON.stringify('hello')); // '"hello"'
console.log(JSON.stringify(true)); // "true"
console.log(JSON.stringify(null)); // "null"
مثال: معامل المسافة للطباعة المنسقة
const data = {
name: 'Alice',
age: 30,
hobbies: ['reading', 'coding'],
address: {
city: 'New York',
country: 'USA'
}
};
// بدون تنسيق (مضغوط)
console.log(JSON.stringify(data));
// {"name":"Alice","age":30,"hobbies":["reading","coding"],"address":{"city":"New York","country":"USA"}}
// مع مسافتين بادئتين
console.log(JSON.stringify(data, null, 2));
// {
// "name": "Alice",
// "age": 30,
// "hobbies": [
// "reading",
// "coding"
// ],
// "address": {
// "city": "New York",
// "country": "USA"
// }
// }
// مع 4 مسافات بادئة
console.log(JSON.stringify(data, null, 4));
// مع مسافة تبويب
console.log(JSON.stringify(data, null, '\t'));
// فاصل نصي مخصص (حتى 10 أحرف)
console.log(JSON.stringify(data, null, '--'));
// {
// --"name": "Alice",
// --"age": 30,
// ...
// }
مثال: دالة ومصفوفة المستبدل (Replacer)
const user = {
name: 'Alice',
password: 'secret123',
email: 'alice@example.com',
age: 30,
role: 'admin'
};
// المستبدل كمصفوفة -- تضمين المفاتيح المحددة فقط
const publicJson = JSON.stringify(user, ['name', 'email', 'role']);
console.log(publicJson);
// '{"name":"Alice","email":"alice@example.com","role":"admin"}'
// المستبدل كدالة -- تحويل أو استبعاد القيم
const safeJson = JSON.stringify(user, (key, value) => {
// استبعاد الحقول الحساسة
if (key === 'password') return undefined;
// تحويل القيم
if (key === 'email') return value.replace(/@.*/, '@***');
return value;
});
console.log(safeJson);
// '{"name":"Alice","email":"alice@***","age":30,"role":"admin"}'
// دالة المستبدل تستقبل القيم المتداخلة أيضًا
const nested = {
level1: {
level2: {
secret: 'hidden',
data: 'visible'
}
}
};
const filtered = JSON.stringify(nested, (key, value) => {
if (key === 'secret') return undefined;
return value;
}, 2);
console.log(filtered);
// {
// "level1": {
// "level2": {
// "data": "visible"
// }
// }
// }
معالجة القيم الخاصة في JSON
ليست كل قيم JavaScript يمكن تمثيلها في JSON. فهم كيفية تعامل JSON.stringify() مع هذه الحالات الحدية أمر بالغ الأهمية لتجنب فقدان البيانات أو السلوك غير المتوقع عند تسلسل الكائنات.
مثال: كيف يتعامل JSON.stringify() مع القيم الخاصة
// undefined -- يُحذف من الكائنات ويُحوّل إلى null في المصفوفات
console.log(JSON.stringify({ a: undefined, b: 1 }));
// '{"b":1}' -- يُحذف a بالكامل
console.log(JSON.stringify([undefined, 1, undefined]));
// '[null,1,null]' -- يُستبدل بـ null في المصفوفات
// الدوال -- تُحذف من الكائنات وتصبح null في المصفوفات
console.log(JSON.stringify({ greet: function() {} }));
// '{}' -- الدالة تُحذف
console.log(JSON.stringify([function() {}, 1]));
// '[null,1]' -- تُستبدل بـ null
// Symbol -- يُحذف من الكائنات
console.log(JSON.stringify({ [Symbol('id')]: 1, name: 'test' }));
// '{"name":"test"}' -- مفتاح Symbol يُحذف
console.log(JSON.stringify({ key: Symbol('val') }));
// '{}' -- قيمة Symbol تتسبب في حذف المفتاح
// الأرقام الخاصة
console.log(JSON.stringify(Infinity)); // "null"
console.log(JSON.stringify(-Infinity)); // "null"
console.log(JSON.stringify(NaN)); // "null"
// BigInt يطرح خطأ
try {
JSON.stringify(42n);
} catch (e) {
console.log(e.message);
// "Do not know how to serialize a BigInt"
}
// كائنات Date -- تُحوّل إلى تمثيل سلسلة ISO الخاص بها
const date = new Date('2024-01-15T12:00:00Z');
console.log(JSON.stringify(date));
// '"2024-01-15T12:00:00.000Z"'
// RegExp -- يُحوّل إلى كائن فارغ
console.log(JSON.stringify(/hello/gi));
// '{}' -- regex يصبح كائنًا فارغًا
// Map و Set -- يُحوّلان إلى كائنات فارغة
console.log(JSON.stringify(new Map([['a', 1]])));
// '{}' -- بيانات Map تُفقد
console.log(JSON.stringify(new Set([1, 2, 3])));
// '{}' -- بيانات Set تُفقد
Map أو Set أو Date أو BigInt، نفّذ دالة toJSON() مخصصة على كائناتك أو استخدم دالة مستبدل في JSON.stringify() مدمجة مع دالة إحياء في JSON.parse() للتعامل مع تحويل الذهاب والإياب.دالة toJSON()
إذا عرّف كائن دالة toJSON()، ستستدعيها JSON.stringify() وتستخدم القيمة المُرجعة للتسلسل بدلاً من الكائن نفسه. هذا يمنحك تحكمًا كاملاً في كيفية تمثيل كائناتك في JSON.
مثال: دالة toJSON() مخصصة
// فئة مخصصة مع toJSON
class User {
constructor(name, email, password) {
this.name = name;
this.email = email;
this.password = password;
this.createdAt = new Date();
}
toJSON() {
return {
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString(),
// كلمة المرور مستبعدة عمدًا
};
}
}
const user = new User('Alice', 'alice@example.com', 'secret123');
console.log(JSON.stringify(user, null, 2));
// {
// "name": "Alice",
// "email": "alice@example.com",
// "createdAt": "2024-01-15T12:00:00.000Z"
// }
// Date لديها دالة toJSON() مدمجة بالفعل
const d = new Date('2024-01-15');
console.log(d.toJSON()); // "2024-01-15T00:00:00.000Z"
// تسلسل مخصص للأنواع المعقدة
class Money {
constructor(amount, currency) {
this.amount = amount;
this.currency = currency;
}
toJSON() {
return `${this.amount} ${this.currency}`;
}
}
const price = new Money(29.99, 'USD');
console.log(JSON.stringify({ price }));
// '{"price":"29.99 USD"}'
JSON مع التواريخ
التعامل مع التواريخ هو أحد أصعب جوانب العمل مع JSON. نظرًا لعدم وجود نوع تاريخ أصلي في JSON، تُسلسل التواريخ عادةً كسلاسل ISO 8601. تحتاج إلى تحويلها صراحةً مرة أخرى إلى كائنات Date عند التحليل. يتطلب تحويل الذهاب والإياب هذا معالجة دقيقة لتجنب الأخطاء.
مثال: تسلسل وإلغاء تسلسل التواريخ
// التواريخ تُحوّل تلقائيًا إلى سلاسل ISO
const event = {
name: 'مؤتمر',
startDate: new Date('2024-06-15T09:00:00Z'),
endDate: new Date('2024-06-17T17:00:00Z')
};
const json = JSON.stringify(event, null, 2);
console.log(json);
// {
// "name": "مؤتمر",
// "startDate": "2024-06-15T09:00:00.000Z",
// "endDate": "2024-06-17T17:00:00.000Z"
// }
// التحليل بدون دالة إحياء -- التواريخ تبقى كسلاسل نصية
const parsed = JSON.parse(json);
console.log(typeof parsed.startDate); // "string"
console.log(parsed.startDate instanceof Date); // false
// استخدام دالة إحياء لاستعادة كائنات Date
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/;
const restored = JSON.parse(json, (key, value) => {
if (typeof value === 'string' && ISO_DATE_REGEX.test(value)) {
return new Date(value);
}
return value;
});
console.log(restored.startDate instanceof Date); // true
console.log(restored.startDate.getFullYear()); // 2024
// دالة مساعدة للتحليل مع دعم التواريخ
function parseWithDates(jsonStr) {
return JSON.parse(jsonStr, (key, value) => {
if (typeof value === 'string') {
const date = new Date(value);
if (!isNaN(date.getTime()) && ISO_DATE_REGEX.test(value)) {
return date;
}
}
return value;
});
}
الاستنساخ العميق باستخدام JSON
أحد الاستخدامات الشائعة لزوج JSON.parse و JSON.stringify هو إنشاء نسخ عميقة من الكائنات. تنشئ هذه التقنية نسخة مستقلة تمامًا بدون مراجع مشتركة مع الأصل. ومع ذلك، لها قيود يجب فهمها قبل استخدامها.
مثال: الاستنساخ العميق للكائنات
// مشكلة النسخ السطحي مقابل النسخ العميق
const original = {
name: 'Alice',
scores: [95, 87, 92],
address: { city: 'New York', zip: '10001' }
};
// النسخ السطحي -- الكائنات المتداخلة لا تزال مشتركة
const shallow = { ...original };
shallow.scores.push(100);
console.log(original.scores); // [95, 87, 92, 100] -- الأصل تعدّل!
// الاستنساخ العميق باستخدام JSON
const deep = JSON.parse(JSON.stringify(original));
deep.scores.push(88);
deep.address.city = 'Boston';
console.log(original.scores); // [95, 87, 92, 100] -- لم يتغير
console.log(original.address.city); // "New York" -- لم يتغير
// قيود الاستنساخ العميق باستخدام JSON
const problematic = {
date: new Date(), // يصبح سلسلة نصية
regex: /pattern/gi, // يصبح {}
func: function() {}, // يُحذف
undef: undefined, // يُحذف
nan: NaN, // يصبح null
infinity: Infinity, // يصبح null
map: new Map([['a', 1]]), // يصبح {}
set: new Set([1, 2, 3]), // يصبح {}
};
const cloned = JSON.parse(JSON.stringify(problematic));
console.log(cloned);
// {date: "2024-...", regex: {}, nan: null, infinity: null, map: {}, set: {}}
// func و undef اختفيا تمامًا!
// البديل الحديث: structuredClone (يتعامل مع أنواع أكثر)
const betterClone = structuredClone(original);
// structuredClone يتعامل مع Date و RegExp و Map و Set و ArrayBuffer إلخ
// لكنه لا يزال لا يستطيع استنساخ الدوال أو عناصر DOM
JSON.parse(JSON.stringify(obj)) للاستنساخ العميق مريح لكنه يفقد كائنات Date و RegExp و Map و Set والدوال وقيم undefined. في JavaScript الحديث، يُفضل استخدام structuredClone() الذي يتعامل مع أنواع أكثر بشكل صحيح. خصص طريقة JSON للكائنات البيانية البسيطة التي تحتوي فقط على قيم متوافقة مع JSON.العمل مع استجابات API
أحد أكثر الاستخدامات شيوعًا لـ JSON هو التواصل مع واجهات REST APIs. يستخدم JavaScript الحديث واجهة fetch() التي تتضمن دوالاً مدمجة لتحليل استجابات JSON وإرسال بيانات JSON. فهم دورة الطلب والاستجابة الكاملة مع JSON أمر ضروري لأي مطور ويب.
مثال: جلب وإرسال بيانات JSON
// جلب بيانات JSON من API
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
// التحقق مما إذا كانت الاستجابة ناجحة (الحالة 200-299)
if (!response.ok) {
throw new Error(`خطأ HTTP! الحالة: ${response.status}`);
}
// تحليل جسم استجابة JSON
const users = await response.json();
// response.json() مكافئ لـ:
// JSON.parse(await response.text())
console.log(users);
return users;
} catch (error) {
console.error('فشل جلب المستخدمين:', error.message);
return [];
}
}
// إرسال بيانات JSON إلى API
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'فشل الطلب');
}
const newUser = await response.json();
console.log('المستخدم المُنشأ:', newUser);
return newUser;
} catch (error) {
console.error('فشل إنشاء المستخدم:', error.message);
return null;
}
}
// الاستخدام
createUser({
name: 'Alice',
email: 'alice@example.com',
role: 'editor'
});
// معالجة استجابات API المقسمة إلى صفحات
async function fetchAllPages(baseUrl) {
const allData = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${baseUrl}?page=${page}&limit=50`);
const result = await response.json();
allData.push(...result.data);
hasMore = result.data.length === 50;
page++;
}
return allData;
}
JSON مقابل كائنات JavaScript
بينما صيغة JSON مبنية على كائنات JavaScript، هناك اختلافات مهمة تُربك المطورين بانتظام. فهم هذه الاختلافات يمنع الأخطاء الشائعة عند التبديل بين التنسيقين.
مثال: الاختلافات الرئيسية بين JSON وكائنات JavaScript
// 1. المفاتيح يجب أن تكون بعلامات اقتباس مزدوجة في JSON
// JavaScript: { name: "Alice" } -- صالح
// JSON: { "name": "Alice" } -- يجب اقتباس المفاتيح
// 2. السلاسل النصية يجب أن تستخدم علامات اقتباس مزدوجة في JSON
// JavaScript: { name: 'Alice' } -- صالح بعلامات مفردة
// JSON: { "name": "Alice" } -- علامات مزدوجة فقط
// 3. لا فواصل زائدة في JSON
// JavaScript: { a: 1, b: 2, } -- فاصلة زائدة صالحة
// JSON: { "a": 1, "b": 2 } -- بدون فاصلة زائدة
// 4. لا تعليقات في JSON
// JavaScript: { /* تعليق */ name: "Alice" } -- صالح
// JSON: لا يُسمح بالتعليقات
// 5. لا مفاتيح محسوبة في JSON
// JavaScript: { [key]: value } -- صالح
// JSON: المفاتيح يجب أن تكون سلاسل حرفية
// 6. اختلافات الأرقام
// JavaScript: 0xFF, 0o77, 0b1010 -- ست عشري وثماني وثنائي
// JSON: أرقام عشرية فقط، بدون أصفار بادئة
// مرجع سريع للتحويل
const obj = { name: 'Alice', age: 30 };
// كائن إلى سلسلة JSON
const json = JSON.stringify(obj);
// سلسلة JSON إلى كائن
const back = JSON.parse(json);
// التحقق من المساواة العميقة لكائنين باستخدام JSON
function deepEqual(obj1, obj2) {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
console.log(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })); // true
console.log(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })); // false
// ملاحظة: ترتيب المفاتيح مهم في مقارنة JSON!
deepEqual() التي تستخدم JSON.stringify مريحة لكنها غير موثوقة تمامًا. تعتمد على تناسق ترتيب المفاتيح ولا يمكنها مقارنة الكائنات التي تحتوي على قيم غير متوافقة مع JSON. للتحقق القوي من المساواة العميقة، استخدم مكتبة مثل Lodash أو اكتب دالة مقارنة تعاودية.التخزين المحلي مع JSON
يمكن لواجهة تخزين الويب (localStorage و sessionStorage) تخزين قيم السلاسل النصية فقط. لتخزين هياكل بيانات معقدة مثل الكائنات والمصفوفات، يجب تسلسلها باستخدام JSON.stringify() قبل الحفظ وإلغاء تسلسلها باستخدام JSON.parse() عند الاسترجاع. هذا النمط أساسي لاستمرار البيانات من جانب العميل.
مثال: تخزين واسترجاع البيانات المعقدة
// غلاف بسيط لـ localStorage مع JSON
const storage = {
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error('خطأ في التخزين:', error.message);
return false;
}
},
get(key, fallback = null) {
try {
const item = localStorage.getItem(key);
return item !== null ? JSON.parse(item) : fallback;
} catch (error) {
console.error('خطأ في الاسترجاع:', error.message);
return fallback;
}
},
remove(key) {
localStorage.removeItem(key);
},
has(key) {
return localStorage.getItem(key) !== null;
}
};
// تخزين أنواع بيانات مختلفة
storage.set('user', { name: 'Alice', theme: 'dark' });
storage.set('scores', [95, 87, 92, 88]);
storage.set('isLoggedIn', true);
storage.set('lastVisit', new Date().toISOString());
// استرجاع البيانات مع أمان النوع
const user = storage.get('user', { name: 'ضيف', theme: 'light' });
console.log(user.name); // "Alice"
console.log(user.theme); // "dark"
const scores = storage.get('scores', []);
console.log(scores.reduce((a, b) => a + b, 0) / scores.length); // المتوسط
// إدارة سلة التسوق
function addToCart(product) {
const cart = storage.get('cart', []);
const existing = cart.find(item => item.id === product.id);
if (existing) {
existing.quantity += 1;
} else {
cart.push({ ...product, quantity: 1 });
}
storage.set('cart', cart);
return cart;
}
function getCartTotal() {
const cart = storage.get('cart', []);
return cart.reduce((total, item) => total + item.price * item.quantity, 0);
}
// إدارة تفضيلات المستخدم
function updatePreferences(updates) {
const prefs = storage.get('preferences', {
theme: 'light',
fontSize: 16,
language: 'ar',
notifications: true
});
const updated = { ...prefs, ...updates };
storage.set('preferences', updated);
return updated;
}
البث والبيانات الكبيرة في JSON
عند العمل مع مجموعات بيانات JSON كبيرة جدًا، تحميل كل شيء في الذاكرة دفعة واحدة قد يكون مشكلة. توفر JavaScript الحديثة إمكانيات البث وتقنيات للتعامل مع حمولات JSON الكبيرة بكفاءة. فهم هذه الأنماط مهم لبناء تطبيقات عالية الأداء.
مثال: معالجة استجابات JSON الكبيرة
// بث JSON باستخدام Fetch API و ReadableStream
async function processLargeResponse(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// معالجة أسطر JSON الكاملة (تنسيق NDJSON)
const lines = buffer.split('\n');
buffer = lines.pop(); // الاحتفاظ بالسطر غير المكتمل في المخزن المؤقت
for (const line of lines) {
if (line.trim()) {
try {
const item = JSON.parse(line);
processItem(item);
} catch (e) {
console.warn('تخطي سطر غير صالح:', line);
}
}
}
}
}
function processItem(item) {
console.log('معالجة:', item.id);
}
// معالجة JSON مجزأة للمصفوفات الكبيرة
function processInChunks(jsonArray, chunkSize, processor) {
let index = 0;
function processChunk() {
const chunk = jsonArray.slice(index, index + chunkSize);
chunk.forEach(processor);
index += chunkSize;
if (index < jsonArray.length) {
// التنازل لحلقة الأحداث بين القطع
setTimeout(processChunk, 0);
}
}
processChunk();
}
// الاستخدام
const largeArray = new Array(10000).fill(null).map((_, i) => ({
id: i,
value: Math.random()
}));
processInChunks(largeArray, 100, (item) => {
// معالجة كل عنصر دون حجب واجهة المستخدم
document.getElementById('progress').textContent = `معالجة ${item.id}...`;
});
// تنسيق JSON Lines (NDJSON) -- كائن JSON واحد لكل سطر
// مفيد للبث وملفات السجلات
const ndjson = `{"id":1,"name":"Alice"}
{"id":2,"name":"Bob"}
{"id":3,"name":"Charlie"}`;
const records = ndjson
.split('\n')
.filter(line => line.trim())
.map(line => JSON.parse(line));
console.log(records.length); // 3
أمثلة عملية لتبادل البيانات
دعنا ننظر إلى سيناريوهات واقعية شاملة تجمع تقنيات JSON المتعددة في حلول عملية ستصادفها في التطوير المهني.
مثال: إدارة التكوين
// تكوين التطبيق مع الافتراضيات والتجاوزات
class Config {
#defaults;
#overrides;
constructor(defaults = {}) {
this.#defaults = defaults;
this.#overrides = {};
this.load();
}
load() {
try {
const saved = localStorage.getItem('app_config');
if (saved) {
this.#overrides = JSON.parse(saved);
}
} catch (e) {
console.warn('فشل تحميل التكوين، استخدام الافتراضيات');
this.#overrides = {};
}
}
save() {
localStorage.setItem('app_config', JSON.stringify(this.#overrides));
}
get(key) {
return key in this.#overrides ? this.#overrides[key] : this.#defaults[key];
}
set(key, value) {
this.#overrides[key] = value;
this.save();
}
reset() {
this.#overrides = {};
this.save();
}
toJSON() {
return { ...this.#defaults, ...this.#overrides };
}
export() {
return JSON.stringify(this.toJSON(), null, 2);
}
import(jsonStr) {
try {
this.#overrides = JSON.parse(jsonStr);
this.save();
return true;
} catch (e) {
console.error('تنسيق تكوين غير صالح');
return false;
}
}
}
const config = new Config({
theme: 'light',
fontSize: 16,
autoSave: true,
maxRetries: 3
});
config.set('theme', 'dark');
console.log(config.get('theme')); // "dark"
console.log(config.export());
مثال: خط أنابيب تحويل البيانات
// تحويل بيانات استجابة API إلى تنسيق التطبيق
function transformUserResponse(apiJson) {
const data = typeof apiJson === 'string' ? JSON.parse(apiJson) : apiJson;
return data.users.map(user => ({
id: user.id,
displayName: `${user.first_name} ${user.last_name}`,
email: user.email_address,
avatar: user.profile_image_url || '/default-avatar.png',
joinedAt: new Date(user.created_at),
isVerified: user.email_verified === true,
permissions: user.roles.flatMap(role => role.permissions || []),
}));
}
// استجابة API (عادةً من fetch().json())
const apiResponse = {
users: [
{
id: 1,
first_name: 'Alice',
last_name: 'Johnson',
email_address: 'alice@example.com',
profile_image_url: 'https://example.com/alice.jpg',
created_at: '2024-01-15T12:00:00Z',
email_verified: true,
roles: [{ name: 'admin', permissions: ['read', 'write', 'delete'] }]
}
]
};
const users = transformUserResponse(apiResponse);
console.log(JSON.stringify(users, null, 2));
// التسلسل للتخزين المؤقت
const cacheKey = 'users_cache';
const cacheData = {
data: users,
cachedAt: new Date().toISOString(),
expiresIn: 300000 // 5 دقائق
};
localStorage.setItem(cacheKey, JSON.stringify(cacheData, (key, value) => {
// تحويل كائنات Date إلى سلاسل ISO للتخزين
if (value instanceof Date) return value.toISOString();
return value;
}));
// استرجاع والتحقق من صلاحية التخزين المؤقت
function getCachedData(key) {
try {
const raw = localStorage.getItem(key);
if (!raw) return null;
const cache = JSON.parse(raw);
const cachedTime = new Date(cache.cachedAt).getTime();
const now = Date.now();
if (now - cachedTime > cache.expiresIn) {
localStorage.removeItem(key);
return null; // انتهت صلاحية التخزين المؤقت
}
return cache.data;
} catch (e) {
return null;
}
}
مثال: تسلسل بيانات النماذج وإدارة الحالة
// تحويل بيانات النموذج إلى JSON
function formToJSON(formElement) {
const formData = new FormData(formElement);
const json = {};
for (const [key, value] of formData.entries()) {
// معالجة القيم المتعددة (خانات الاختيار والقوائم المتعددة)
if (key in json) {
if (!Array.isArray(json[key])) {
json[key] = [json[key]];
}
json[key].push(value);
} else {
json[key] = value;
}
}
return json;
}
// إدارة حالة التراجع/الإعادة مع لقطات JSON
class StateManager {
#history = [];
#position = -1;
#maxHistory;
constructor(initialState = {}, maxHistory = 50) {
this.#maxHistory = maxHistory;
this.pushState(initialState);
}
get current() {
return JSON.parse(this.#history[this.#position]);
}
pushState(state) {
// إزالة أي حالات مستقبلية إذا تفرعنا
this.#history = this.#history.slice(0, this.#position + 1);
// إضافة حالة جديدة كلقطة JSON
this.#history.push(JSON.stringify(state));
this.#position++;
// تحديد حجم التاريخ
if (this.#history.length > this.#maxHistory) {
this.#history.shift();
this.#position--;
}
}
undo() {
if (this.#position > 0) {
this.#position--;
return this.current;
}
return null;
}
redo() {
if (this.#position < this.#history.length - 1) {
this.#position++;
return this.current;
}
return null;
}
get canUndo() { return this.#position > 0; }
get canRedo() { return this.#position < this.#history.length - 1; }
}
// الاستخدام
const state = new StateManager({ items: [], count: 0 });
state.pushState({ items: ['تفاحة'], count: 1 });
state.pushState({ items: ['تفاحة', 'موزة'], count: 2 });
console.log(state.current.count); // 2
state.undo();
console.log(state.current.count); // 1
state.redo();
console.log(state.current.count); // 2
مثال: اكتشاف المراجع الدائرية
// JSON.stringify يطرح خطأ عند المراجع الدائرية
const obj = { name: 'Alice' };
obj.self = obj; // مرجع دائري
try {
JSON.stringify(obj);
} catch (e) {
console.log(e.message);
// "Converting circular structure to JSON"
}
// مستبدل مخصص للتعامل مع المراجع الدائرية
function stringifyWithCircular(obj, space = 2) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[مرجع دائري]';
}
seen.add(value);
}
return value;
}, space);
}
const circular = { name: 'Alice', friends: [] };
circular.friends.push(circular); // دائري!
console.log(stringifyWithCircular(circular));
// {
// "name": "Alice",
// "friends": [
// "[مرجع دائري]"
// ]
// }
// نسخة أكثر تقدمًا تُظهر المسار
function safeStringify(obj, space = 2) {
const seen = new Map();
let pathIndex = 0;
return JSON.stringify(obj, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return `[دائري: ${seen.get(value)}]`;
}
seen.set(value, `#ref${pathIndex++}`);
}
return value;
}, space);
}
تمرين عملي
ابنِ نظام إدارة مهام كاملًا يستخدم JSON لجميع عمليات استمرار البيانات والتبادل. أنشئ فئة TaskManager تخزن المهام في localStorage باستخدام تسلسل JSON. يجب أن تحتوي كل مهمة على معرف وعنوان ووصف وأولوية (عالية أو متوسطة أو منخفضة) وحالة (معلقة أو قيد التنفيذ أو مكتملة) وتاريخ إنشاء وتاريخ استحقاق اختياري. نفّذ الميزات التالية: (1) إضافة وتحديث وحذف المهام مع استمرار تلقائي في localStorage. (2) تصدير جميع المهام كسلسلة JSON منسقة يمكن حفظها كملف. (3) استيراد المهام من سلسلة JSON مع تحقق كامل، بالتحقق من وجود جميع الحقول المطلوبة وصلاحية قيمها ومعالجة أخطاء التحليل بسلاسة. (4) دالة بحث تصفّي المهام بالعنوان أو الوصف وتعيد المهام المطابقة. (5) دالة إحصائيات تعيد كائن JSON يحتوي على إجمالي المهام والمهام حسب الحالة والمهام حسب الأولوية وعدد المهام المتأخرة. (6) تنفيذ وظيفة التراجع باستخدام لقطات JSON للحالة حتى يتمكن المستخدمون من عكس آخر خمس إجراءات. (7) اكتب دالة ترحيل بيانات يمكنها قراءة بيانات المهام بتنسيق قديم (حيث كانت الأولوية رقمًا 1-3) وتحويلها إلى التنسيق الجديد (مع أولويات نصية). اختبر جميع الوظائف بإنشاء عدة مهام وتعديلها وتصدير البيانات ومسح localStorage واستيراد البيانات المُصدّرة مرة أخرى والتحقق من تطابق كل شيء. اعرض جميع النتائج في وحدة التحكم مع إظهار مخرجات JSON في كل خطوة.