חזרה

תכונות מעניינות בסריאליזציה של אובייקטים ב-JS

כדאי להכיר את JSON.stringify לעומק, פירטתי כאן תכונות שכדאי להכיר ולזכור


בתוכנה, כאשר אנחנו רוצים לקחת ערך כלשהו ולהעביר אותו לגוף חיצוני, נצטרך להמיר את הערך הזה לפורמט סטנדרטי שלא תלוי בשפה הספציפית שלנו.
לדוגמה, כאשר אני מחזיר תשובת HTTP מ-API שלי שכתוב ב-JS לבקשה שקיבלתי, עם נתונים שהוכנו עבור הבקשה הספציפית. אני לא יכול לדעת שמאחורי הבקשה הזו עומד קוד JS. זו יכולה להיות כל שפת קוד אחרת. אני לא יכול לסמוך על זה שהאובייקט JS שאני מחזיק כרגע נתמך אצל שולח הבקשה.
לכן אני צריך לבצע סריאליזציה (או ‘המרה’) של הנתונים הללו (ב-JS זה יכול להיות לדוגמא אובייקט רגיל, או מערך (שב-JS הוא תכלס אובייקט) או פשוט ערך כמו סטרינג או מספר וכו’) לפורמט סטנדרטי. שנתמך אצל כולם. וכמובן בדרך כלל מדובר ב-JSON.

סטנדרט JSON, בקצרה, הוא פורמט של ייצוג נתונים בצורה טקסטואלית.
הוא תומך בסוגי הנתונים דלהלן:

  • אובייקט
  • מערך
  • סטרינג
  • מספר
  • בוליאני
  • ריק/חסר ערך (null)

לכל שפת תוכנה יש את הכלים המובנים/חיצוניים שלה לסריאליזציה/דסריאליזציה של JSON. כאן אעסוק בעיקר ב-JS ובכלי המובנה שבה.
ב-JS מבצעים סריאליזציה ל-JSON על ידי קריאה ל-JSON.stringify. פעולה זו תמיר את האובייקט לסטרינג JSON קביל שיכול לעבור בין יישומים ואפשר לסמוך על זה שכולם יוכלו לקרוא אותו איך שהוא נקרא אצלנו.
אבל מפתחי JS צריכים להכיר את הדרכים שבהן JSON.stringify מתמודד עם ערכים מיוחדים ומצבים שונים. בלי זה, פתאום תגלו שהאובייקט שהמרתם קיבל פורמט שונה לחלוטין ממה שחשבתם. נתחיל מהפשוטים והמצויים:

טיפול ב-undefined

כפי שניתן לראות למעלה, JSON לא תומך בערך undefined, שהוא חידוש ספציפי ב-JS.
ב-JS בחרו לנקוט בדרך שבה undefined מושמט לחלוטין מאובייקט כאשר מבצעים בו סריאליזציה. זה אומר שאם האובייקט שלכם (או מערך וכו’) מכיל ערך undefined, הוא לא יהיה קיים בתוצאה של JSON. דוגמה בקוד:

const obj = {
    name: 'chv',
    isAdmin: true,
    lastSeen: undefined
};

JSON.stringify(obj);  // '{"name":"chv","isAdmin":true}'

חשוב מאוד לזכור את זה, ולדעת שזה לא באג אלא פיצ’ר למי שיודע איך לנצל את זה נכון.
מאידך כאשר מדובר במערך, כאשר נמצא בו undefined בזמן סריאליזציה הוא מומר ל-null.
האמור בפסקה זו נכון גם לפונקציה (Function) ו-Symbol - שניהם יושמטו כאשר נמצאו באובייקט, ויהפכו ל-null כאשר נמצאו במערך, בזמן סריאליזציה.

טיפול במספרים מיוחדים - NaN, Infinity, BigInt

סוג המספרים שבהם JSON תומך כולל רק מספרים פשוטים.
ב-JS בחרו לנקוט בדרך בה מספרים מיוחדים כגון - NaN (מספר לא תקין) או Infinity (אינסוף חיובי/שלילי) מומרים ל-null במקרה של סריאליזציה. זה אומר שאם האובייקט שלכם מכיל ערך NaN או אינסוף - הוא ייוצג כ-null בתוצאה של JSON. דוגמה בקוד:

const obj = {
    name: 'chv',
    age: Infinity
};

JSON.stringify(obj);  // '{"name":"chv","age":null}'

מאידך BigInt - מספר גדול לא נתמך בסריאליזציה ויזרוק שגיאה במקרה של ניסיון סריאליזציה. דוגמה בקוד:

const obj = {
    name: 'chv',
    age: 1n
};

JSON.stringify(obj);  // TypeError

פתרונות לסריאליזציה/דסריאליזציה ל-BigInt אפשר לראות כאן.

טיפול באובייקטים כגון תאריך

כאשר JS מבצעת סריאליזציה לאובייקט, היא מנסה תחילה להפעיל את המתודה toJSON אם קיימת באובייקט. זה אומר שאובייקטים מיוחדים שצריכים תהליך מיוחד כדי להמיר אותם ל-JSON - צריך לכלול בהם את המתודה הזו, שתחזיר ערך שרק עליו תבוצע הסריאליזציה.
כך לדוגמה באובייקט Date של JS, שמכיל מתודת toJSON שמחזירה סטרינג של התאריך בפורמט ISO. כך זה ייראה:

JSON.stringify(new Date());  // '"2025-01-15T21:30:00.000Z"'

לדוגמה אם אני רוצה שבהמרה של אובייקט ל-JSON, הוא לא יכלול ערכי null - אפשר לכתוב אותו כך:

const obj = {
  name: 'chv',
  age: null,
  toJSON () {
    return Object.fromEntries(
      Object.entries(this).filter(([_, value]) => value !== null)
    );
  }
};

JSON.stringify(obj);  // '{"name":"chv"}'

טיפול באובייקטים מיוחדים נוספים

כאשר סריאליזציה מבוצעת על אובייקט, JS מתייחסת רק לתכונות שהן enumerable. אובייקטים מובנים כמו Map שומרים את הנתונים שלהם במבנה פנימי שאינו נגיש דרך תכונות enumerable רגילות.
לכן, כאשר Map מגיעה כערך של תכונה באובייקט, היא תייוצג כאובייקט ריק במקרה של סריאליזציה, בגלל שהתכונות המכילות את הנתונים של ה-Map אינן enumerable. דוגמה בקוד:

const obj = {
  name: 'chv',
  sites: new Map([
    [1, 'site1'],
    [2, 'site2']
  ])
};

JSON.stringify(obj);  // '{"name":"chv","sites":{}}'

כדי לטפל במקרים כאלה אפשר לכתוב מתודת toJSON מותאמת, או להשתמש ב-replacer בזמן הסריאליזציה, כדלהלן.

פרמטרים ואפשרויות ב-JSON.stringify

המתודה JSON.stringify מכילה את הפרמטרים הבאים:

במקרה שצריך להתאים את הסריאליזציה - מספקים פונקציית replacer מתאימה. לדוגמה, כדי להמיר אובייקט ל-JSON ולהשמיט ערכי null כמו שנכתב לפני כן, לא צריך להשתמש במתודת toJSON מיוחדת באובייקט, אם בשלב הסריאליזציה כותבים את הקוד הזה:

const obj = {
  name: 'chv',
  age: null
};

JSON.stringify(obj, (key, value) => {
  if (value === null) {
    return undefined;
  }
  return value;
});  // '{"name":"chv"}'

או במקרה שצריך את הערך של Map, אפשר לכתוב קוד כזה:

const obj = {
  name: 'chv',
  sites: new Map([
    [1, 'site1'],
    [2, 'site2']
  ])
};

JSON.stringify(obj, (key, value) => {
  if (value instanceof Map) {
    return Object.fromEntries([...value]);
  }
  return value;
});  // '{"name":"chv","sites":{"1":"site1","2":"site2"}}'

הפרמטר space הוא אופציונלי ומאפשר שליטה בעיצוב ומבנה טקסט ה-JSON שיווצר.

לסיכום

מומלץ לעבור על התיעוד של JSON.stringify.


אם יש לכם איזו שאלה ❔✨ או כל תגובה 💬, הארה 💡 והערה ❕ שהיא על הפוסט - אשמח מאוד! אם תכתבו אותה בהערות כאן למטה
פשוט להתחבר עם חשבון גיטהב ולהגיב 🎉