العمل مع JSON
JSON (JavaScript Object Notation) هو التنسيق القياسي لتبادل البيانات بين تطبيقات الويب. توفر JavaScript طرقاً مدمجة للتحويل بين كائنات JavaScript وسلاسل JSON النصية، مما يجعلها ضرورية لتطوير الويب الحديث.
فهم JSON
JSON هو تنسيق نصي يشبه كائنات JavaScript لكن بقواعد أكثر صرامة:
// JSON صالح
{
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"isActive": true,
"hobbies": ["reading", "coding", "gaming"],
"address": {
"city": "New York",
"zipCode": "10001"
}
}
// قواعد JSON:
// ✓ يجب أن تكون أسماء الخصائص بين علامات اقتباس مزدوجة
// ✓ يجب أن تستخدم السلاسل النصية علامات اقتباس مزدوجة (لا علامات اقتباس مفردة)
// ✓ لا فواصل زائدة
// ✓ لا تعليقات مسموحة
// ✓ لا undefined أو دوال أو رموز
// ✓ أرقام وقيم منطقية و null وسلاسل نصية ومصفوفات وكائنات فقط
الفرق الرئيسي: كائنات JavaScript أكثر مرونة من JSON. JSON هو مجموعة فرعية من JavaScript بقواعد بناء جملة أكثر صرامة مصممة لتبادل البيانات.
JSON.stringify() - التحويل إلى JSON
JSON.stringify() يحول قيم JavaScript إلى سلاسل JSON نصية:
const user = {
name: "John Doe",
age: 30,
email: "john@example.com",
hobbies: ["reading", "coding"]
};
// تحويل أساسي
const jsonString = JSON.stringify(user);
console.log(jsonString);
// {"name":"John Doe","age":30,"email":"john@example.com","hobbies":["reading","coding"]}
// طباعة جميلة مع مسافة بادئة (مسافتان)
const prettyJson = JSON.stringify(user, null, 2);
console.log(prettyJson);
/*
{
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"hobbies": [
"reading",
"coding"
]
}
*/
// استخدام tabs للمسافة البادئة
const tabbedJson = JSON.stringify(user, null, "\t");
JSON.parse() - تحليل سلاسل JSON النصية
JSON.parse() يحول سلاسل JSON النصية إلى كائنات JavaScript:
const jsonString = '{"name":"John","age":30,"isActive":true}';
// تحليل سلسلة JSON النصية
const user = JSON.parse(jsonString);
console.log(user.name); // "John"
console.log(user.age); // 30
console.log(user.isActive); // true
// تحليل JSON مصفوفة
const arrayJson = '["apple", "banana", "orange"]';
const fruits = JSON.parse(arrayJson);
console.log(fruits[0]); // "apple"
خطأ شائع: JSON.parse() يرمي SyntaxError إذا لم تكن السلسلة النصية JSON صالحة. استخدم دائماً try/catch عند تحليل JSON من مصادر خارجية!
معالجة أخطاء JSON
دائماً لف JSON.parse() في try/catch للتعامل مع JSON غير الصالح بشكل سلس:
function safeJsonParse(jsonString, fallback = null) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error("خطأ تحليل JSON:", error.message);
return fallback;
}
}
// الاستخدام
const validJson = '{"name":"John"}';
const invalidJson = '{name: "John"}'; // غير صالح: لا علامات اقتباس حول المفتاح
const user1 = safeJsonParse(validJson);
console.log(user1); // { name: "John" }
const user2 = safeJsonParse(invalidJson, { name: "Default" });
console.log(user2); // { name: "Default" }
// معالجة أخطاء أكثر تفصيلاً
function parseJsonWithDetails(jsonString) {
try {
return {
success: true,
data: JSON.parse(jsonString),
error: null
};
} catch (error) {
return {
success: false,
data: null,
error: error.message
};
}
}
const result = parseJsonWithDetails(invalidJson);
if (result.success) {
console.log("البيانات:", result.data);
} else {
console.log("الخطأ:", result.error);
}
معامل Replacer
JSON.stringify() يقبل دالة replacer أو مصفوفة للتحكم في ما يتم تضمينه:
const user = {
name: "John",
password: "secret123",
email: "john@example.com",
age: 30
};
// استخدام مصفوفة لتحديد خصائص محددة
const limitedJson = JSON.stringify(user, ["name", "email"]);
console.log(limitedJson);
// {"name":"John","email":"john@example.com"}
// استخدام دالة لتصفية/تحويل القيم
const filteredJson = JSON.stringify(user, (key, value) => {
// استبعاد حقل كلمة المرور
if (key === "password") {
return undefined;
}
// تحويل العمر إلى سلسلة نصية
if (key === "age") {
return String(value);
}
return value;
});
console.log(filteredJson);
// {"name":"John","email":"john@example.com","age":"30"}
معامل Reviver
JSON.parse() يقبل دالة reviver لتحويل القيم المُحللة:
const jsonString = '{
"name": "John",
"birthDate": "1990-01-15",
"lastLogin": "2024-01-20T10:30:00.000Z"
}';
// تحليل مع reviver لتحويل سلاسل التاريخ إلى كائنات Date
const user = JSON.parse(jsonString, (key, value) => {
// تحقق إذا كانت القيمة تشبه سلسلة تاريخ
if (typeof value === "string") {
const datePattern = /^\d{4}-\d{2}-\d{2}/;
if (datePattern.test(value)) {
return new Date(value);
}
}
return value;
});
console.log(user.birthDate instanceof Date); // true
console.log(user.birthDate.getFullYear()); // 1990
console.log(user.lastLogin instanceof Date); // true
JSON مع أنواع خاصة
بعض أنواع JavaScript تحتاج معالجة خاصة عند التحويل من/إلى JSON:
// التواريخ
const data1 = { date: new Date() };
const json1 = JSON.stringify(data1);
console.log(json1); // {"date":"2024-01-20T10:30:00.000Z"}
const parsed1 = JSON.parse(json1);
console.log(typeof parsed1.date); // "string" (ليس Date!)
console.log(new Date(parsed1.date)); // تحويل إلى Date مرة أخرى
// قيم undefined تُحذف
const data2 = { name: "John", age: undefined, email: "john@example.com" };
console.log(JSON.stringify(data2));
// {"name":"John","email":"john@example.com"}
// الدوال تُحذف
const data3 = { name: "John", greet: function() { return "Hi"; } };
console.log(JSON.stringify(data3));
// {"name":"John"}
// NaN و Infinity يصبحان null
const data4 = { value: NaN, infinite: Infinity };
console.log(JSON.stringify(data4));
// {"value":null,"infinite":null}
// المصفوفات تحتفظ بـ undefined كـ null
const arr = [1, undefined, 3];
console.log(JSON.stringify(arr));
// [1,null,3]
أفضل ممارسة: عند العمل مع التواريخ في JSON، قم دائماً بتخزينها كسلاسل نصية ISO 8601 وقم بتحويلها مرة أخرى إلى كائنات Date بعد التحليل باستخدام دالة reviver.
الاستنساخ العميق باستخدام JSON
يمكن استخدام JSON لإنشاء نسخ عميقة من الكائنات (مع قيود):
const original = {
name: "John",
hobbies: ["reading", "coding"],
address: {
city: "New York"
}
};
// استنساخ عميق باستخدام JSON
const clone = JSON.parse(JSON.stringify(original));
// تعديل النسخة
clone.hobbies.push("gaming");
clone.address.city = "Boston";
console.log(original.hobbies); // ["reading", "coding"] - لم يتغير!
console.log(original.address.city); // "New York" - لم يتغير!
console.log(clone.hobbies); // ["reading", "coding", "gaming"]
console.log(clone.address.city); // "Boston"
// ⚠️ القيود:
const complexObject = {
date: new Date(),
regex: /test/,
func: () => {},
undef: undefined,
symbol: Symbol("test"),
circularRef: null
};
complexObject.circularRef = complexObject;
// هذا يفقد التاريخ والتعبير النمطي والدالة و undefined والرمز
// ويرمي خطأ على المرجع الدائري!
// const cloneComplex = JSON.parse(JSON.stringify(complexObject)); // خطأ!
تحذير: استنساخ JSON يعمل فقط للأنواع البسيطة (سلاسل نصية، أرقام، قيم منطقية، null، كائنات بسيطة، ومصفوفات). يفقد الدوال والتواريخ (يحول إلى سلاسل نصية) و undefined والرموز، ويرمي أخطاء على المراجع الدائرية.
أنماط JSON من العالم الحقيقي
// 1. معالجة استجابة API
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const jsonText = await response.text(); // احصل على نص أولاً
// تحقق إذا كانت الاستجابة JSON صالحة
if (!jsonText) {
throw new Error("استجابة فارغة");
}
const userData = JSON.parse(jsonText);
return userData;
} catch (error) {
console.error("فشل جلب المستخدم:", error);
throw error;
}
}
// 2. LocalStorage مع JSON
const storage = {
set(key, value) {
try {
const jsonString = JSON.stringify(value);
localStorage.setItem(key, jsonString);
} catch (error) {
console.error("فشل الحفظ في localStorage:", error);
}
},
get(key, fallback = null) {
try {
const jsonString = localStorage.getItem(key);
return jsonString ? JSON.parse(jsonString) : fallback;
} catch (error) {
console.error("فشل القراءة من localStorage:", error);
return fallback;
}
},
remove(key) {
localStorage.removeItem(key);
}
};
// الاستخدام
storage.set("user", { name: "John", age: 30 });
const user = storage.get("user");
console.log(user); // { name: "John", age: 30 }
// 3. بيانات النموذج إلى JSON
function formToJson(formElement) {
const formData = new FormData(formElement);
const jsonObject = {};
formData.forEach((value, key) => {
// معالجة قيم متعددة (مثل مربعات الاختيار)
if (jsonObject[key]) {
if (!Array.isArray(jsonObject[key])) {
jsonObject[key] = [jsonObject[key]];
}
jsonObject[key].push(value);
} else {
jsonObject[key] = value;
}
});
return jsonObject;
}
// الاستخدام
const form = document.querySelector("#myForm");
const formJson = JSON.stringify(formToJson(form));
console.log(formJson);
التحقق من صحة JSON
// تحقق إذا كانت السلسلة JSON صالحة
function isValidJson(str) {
try {
JSON.parse(str);
return true;
} catch {
return false;
}
}
console.log(isValidJson('{"name":"John"}')); // true
console.log(isValidJson('{name: "John"}')); // false
// التحقق من بنية JSON
function validateUserJson(jsonString) {
try {
const user = JSON.parse(jsonString);
// تحقق من الحقول المطلوبة
if (!user.name || typeof user.name !== "string") {
throw new Error("اسم غير صالح أو مفقود");
}
if (!user.email || !user.email.includes("@")) {
throw new Error("بريد إلكتروني غير صالح أو مفقود");
}
if (user.age && (typeof user.age !== "number" || user.age < 0)) {
throw new Error("عمر غير صالح");
}
return { valid: true, data: user };
} catch (error) {
return { valid: false, error: error.message };
}
}
// الاستخدام
const result1 = validateUserJson('{"name":"John","email":"john@example.com","age":30}');
console.log(result1); // { valid: true, data: {...} }
const result2 = validateUserJson('{"name":"John","email":"invalid"}');
console.log(result2); // { valid: false, error: "بريد إلكتروني غير صالح أو مفقود" }
تمرين تطبيقي:
المهمة: أنشئ فئة أداة مساعدة للعمل بأمان مع JSON تتضمن معالجة الأخطاء وتحويل الأنواع.
// مهمتك: تنفيذ هذه الفئة
class JsonUtil {
static stringify(value, options = {}) {
// تحويل إلى JSON مع معالجة الأخطاء
// دعم خيار الطباعة الجميلة
}
static parse(jsonString, options = {}) {
// تحليل JSON مع معالجة الأخطاء
// دعم reviver لتحويل التاريخ
// دعم قيمة fallback افتراضية
}
static isValid(str) {
// تحقق إذا كانت السلسلة JSON صالحة
}
static clone(obj) {
// استنساخ عميق للكائن باستخدام JSON
// معالجة الأخطاء
}
}
الحل:
class JsonUtil {
static stringify(value, options = {}) {
const { pretty = false, space = 2 } = options;
try {
return JSON.stringify(value, null, pretty ? space : 0);
} catch (error) {
console.error("خطأ JSON stringify:", error.message);
return null;
}
}
static parse(jsonString, options = {}) {
const { convertDates = false, fallback = null } = options;
try {
const reviver = convertDates
? (key, value) => {
if (typeof value === "string") {
const datePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
if (datePattern.test(value)) {
return new Date(value);
}
}
return value;
}
: undefined;
return JSON.parse(jsonString, reviver);
} catch (error) {
console.error("خطأ JSON parse:", error.message);
return fallback;
}
}
static isValid(str) {
if (typeof str !== "string") return false;
try {
JSON.parse(str);
return true;
} catch {
return false;
}
}
static clone(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (error) {
console.error("خطأ JSON clone:", error.message);
return null;
}
}
}
// الاستخدام
const obj = { name: "John", date: new Date().toISOString() };
const json = JsonUtil.stringify(obj, { pretty: true });
console.log(json);
const parsed = JsonUtil.parse(json, { convertDates: true });
console.log(parsed.date instanceof Date); // true
console.log(JsonUtil.isValid('{"name":"John"}')); // true
console.log(JsonUtil.isValid('{invalid}')); // false
const cloned = JsonUtil.clone(obj);
console.log(cloned);
الملخص
في هذا الدرس، تعلمت:
- JSON هو تنسيق نصي لتبادل البيانات مع قواعد بناء جملة صارمة
- JSON.stringify() يحول JavaScript إلى سلاسل JSON نصية
- JSON.parse() يحول سلاسل JSON النصية إلى كائنات JavaScript
- استخدم دائماً try/catch عند تحليل JSON من مصادر خارجية
- دوال Replacer و reviver توفر التحكم في التحويل
- الأنواع الخاصة (التواريخ، الدوال، undefined) تحتاج معالجة دقيقة
- JSON يمكن أن ينشئ نسخ عميقة لكن له قيود
- التحقق من بنية JSON للتطبيقات القوية
التالي: في الدرس التالي، سنستكشف الأنماط غير المتزامنة المتقدمة بما في ذلك التكرار غير المتزامن والمولدات واستراتيجيات معالجة الأخطاء المتطورة!