תכונות מעניינות בסריאליזציה של אובייקטים ב-JS
- תוכן העניינים:
⊙ טיפול ב-undefined
⊙ טיפול במספרים מיוחדים - NaN, Infinity, BigInt
⊙ טיפול באובייקטים כגון תאריך
⊙ טיפול באובייקטים מיוחדים נוספים
⊙ פרמטרים ואפשרויות ב-JSON.stringify
⊙ לסיכום
כדאי להכיר את 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
.