JavaScript المتقدم (ES6+)

العمل مع JSON

13 دقيقة الدرس 18 من 40

العمل مع 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 للتطبيقات القوية
التالي: في الدرس التالي، سنستكشف الأنماط غير المتزامنة المتقدمة بما في ذلك التكرار غير المتزامن والمولدات واستراتيجيات معالجة الأخطاء المتطورة!