המחלקה `JSON` ב-JavaScript

המחלקה `JSON` ב-JavaScript

מאמר זה מסביר את המחלקה JSON ב-JavaScript.

נסביר את המחלקה JSON ב-JavaScript עם דוגמאות מעשיות.

YouTube Video

המחלקה JSON ב-JavaScript

לאובייקט JSON יש בעיקר שתי מתודות. JSON.stringify() ממיר אובייקט למחרוזת JSON, ו-JSON.parse() יוצר אובייקט ממחרוזת JSON. JSON הוא פורמט להחלפת נתונים שבו ניתן לייצג רק חלק מהערכים של JavaScript. פונקציות, undefined, Symbol והפניות מעגליות לא ניתנות להמרה ישירה ל-JSON.

JSON.stringify()

ראשית, הנה דוגמה בסיסית של המרת אובייקט למחרוזת JSON.

1// Convert a JavaScript object to a JSON string.
2const obj = { name: "Alice", age: 30, active: true };
3const jsonString = JSON.stringify(obj);
4console.log(jsonString); // {"name":"Alice","age":30,"active":true}
  • הקוד הזה הוא הדוגמה הבסיסית ביותר להמרת אובייקט רגיל למחרוזת JSON. JSON.stringify מחזיר את המחרוזת באופן סינכרוני.

הארגומנט השני של JSON.stringify (replacer)

על ידי ציון replacer, הארגומנט השני של JSON.stringify, ניתן לשלוט באופן מדויק בכללי ההמרה. להלן דוגמה בה נעשה שימוש במערך כדי לשמור רק תכונות מסוימות.

1// Use a replacer array to include only specific properties during stringification.
2const user = { id: 1, name: "Bob", password: "secret", role: "admin" };
3const safeJson = JSON.stringify(user, ["id", "name", "role"]);
4console.log(safeJson); // {"id":1,"name":"Bob","role":"admin"}
  • בדוגמה זו, password לא כלול כדי לאפשר רישום לוג בטוח. זה שימושי כאשר יש להסיר מידע רגיש מסיבות אבטחה.

שימוש ב-replacer כפונקציה (להמרת ערכים וסינון)

אם ברצונך בגמישות רבה יותר, ניתן לספק פונקציה לעיבוד כל מפתח וערך. להלן דוגמה לשמירת ערך Date כמחרוזת ISO.

 1// Use a replacer function to customize serialization.
 2const data = { name: "Carol", joined: new Date("2020-01-01T12:00:00Z") };
 3const jsonCustom = JSON.stringify(data, (key, value) => {
 4  // Convert Date objects to ISO strings explicitly
 5  if (this && this[key] instanceof Date) {
 6    return this[key].toISOString();
 7  }
 8  return value;
 9});
10console.log(jsonCustom); // {"name":"Carol","joined":"2020-01-01T12:00:00.000Z"}
  • ב-replacer מסוג פונקציה, מועברים המפתח והערך והערך המוחזר משמש ב-JSON הסופי. this מתייחס לאובייקט האב ולכן ניתן להשתמש בו גם להמרות מקוננות.

יסודות השימוש ב-JSON.parse()

JSON.parse ממיר מחרוזת JSON חזרה לאובייקט. עטוף זאת ב-try/catch כדי לטפל ב-JSON לא תקין.

1// Parse a JSON string into an object and handle parsing errors.
2const jsonText = '{"title":"Example","count":42}';
3try {
4  const parsed = JSON.parse(jsonText);
5  console.log(parsed.title); // Example
6} catch (err) {
7  console.error("Invalid JSON:", err.message);
8}
  • אם המחרוזת אינה תקינה, תיזרק חריגה, לכן תמיד הוסף טיפול בחריגות כאשר עובדים עם מקורות חיצוניים.

שחזור מותאם אישית בעזרת reviver (דוגמה לשחזור Date)

על ידי שימוש בפרמטר השני reviver של JSON.parse, ניתן לבצע שחזור ערכים. להלן דוגמה להמרת תאריך השמור כמחרוזת חזרה לאובייקט Date.

 1// Use a reviver to turn ISO date strings back into Date objects.
 2const jsonWithDate = '{"name":"Dana","joined":"2021-06-15T09:00:00.000Z"}';
 3const obj = JSON.parse(jsonWithDate, (key, value) => {
 4  // A simple check for ISO date-like strings
 5  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
 6    return new Date(value);
 7  }
 8  return value;
 9});
10console.log(obj.joined instanceof Date); // true
  • reviver מופעל עבור כל מפתח, והערך המוחזר משמש באובייקט הסופי. יש לבצע בדיקה קפדנית של פורמט תאריך בהתאם לאפליקציה שלך.

הדפסה נאה (Pretty-print) ופרמטר ה-space

כדי להפיק פלט עם הזחה לקריאות, העבר מספר או מחרוזת כפרמטר השלישי ל-JSON.stringify.

1// Pretty-print an object with 2-space indentation for readability.
2const config = { host: "localhost", port: 3000, debug: true };
3const pretty = JSON.stringify(config, null, 2);
4console.log(pretty);
5/* {
6  "host": "localhost",
7  "port": 3000,
8  "debug": true
9} */
  • ב-JSON המיועד לקריאה אנושית, כמו לוגים או קבצי תצורה, כדאי להגדיר הזחה. יש לשים לב כי פעולה זו מגדילה את גודל הנתונים.

כיצד להתמודד עם הפניות מעגליות

JSON.stringify יזרוק TypeError אם יש הפניה מעגלית. פתרון נפוץ הוא ליצור replacer שבו נעשה שימוש בסט מסוג seen כדי להימנע מהפניות מעגליות.

 1// Safely stringify objects that may contain circular references.
 2function safeStringify(value) {
 3  const seen = new WeakSet();
 4  return JSON.stringify(value, (key, val) => {
 5    if (val && typeof val === "object") {
 6      if (seen.has(val)) return "[Circular]";
 7      seen.add(val);
 8    }
 9    return val;
10  });
11}
12
13const a = { name: "A" };
14a.self = a;
15console.log(safeStringify(a)); // {"name":"A","self":"[Circular]"}
  • על ידי שימוש ב-WeakSet, ניתן לזהות הפניות מעגליות תוך מניעת דליפות זיכרון. במקרה של הפניה מעגלית, הוא מחזיר "[Circular]". ניתן גם להקצות מזהה הפניה במקום "[Circular]".

סיריאליזציה מותאמת אישית באמצעות מתודת toJSON

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

 1// Define toJSON on a class to customize its JSON representation.
 2class Point {
 3  constructor(x, y) {
 4    this.x = x;
 5    this.y = y;
 6  }
 7  toJSON() {
 8    // This will be used by JSON.stringify
 9    return { x: this.x, y: this.y, type: "Point" };
10  }
11}
12
13const p = new Point(10, 20);
14console.log(JSON.stringify(p)); // {"x":10,"y":20,"type":"Point"}
  • עם toJSON ניתן להגדיר כללי סיריאליזציה ברמת האובייקט, מה שהופך זאת למקומי ואינטואיטיבי יותר מאשר באמצעות replacer.

מגבלות והערות חשובות לגבי JSON

דוגמאות בולטות שלא ניתן להמיר ל-JSON הן undefined, פונקציות, Symbol, BigInt והפניות מעגליות. יש לשים לב גם לדיוק מספרי (כמו מספרים שלמים גדולים ועיגול ערכי נקודה צפה על פי IEEE).

 1// Demonstrate values that can't be represented in JSON.
 2const sample = {
 3  a: undefined,
 4  b: function () {},
 5  c: Symbol("s"),
 6  d: 9007199254740993n // BigInt (note: JSON.stringify will throw on BigInt)
 7};
 8// JSON.stringify will throw for BigInt and will drop undefined, functions, symbols in objects.
 9try {
10  console.log(JSON.stringify(sample));
11} catch (err) {
12  console.error("Error during stringify:", err.message);
13}
  • בעת טיפול ב-BigInt, המר אותו למחרוזת או הגדר את ייצוגו באמצעות replacer או toJSON מותאמים.

אבטחה (בעת עבודה עם JSON לא אמין)

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

1// Parse external JSON and validate expected properties before use.
2const external = '{"username":"eve","role":"user"}';
3const parsed = JSON.parse(external);
4if (typeof parsed.username === "string" && ["user","admin"].includes(parsed.role)) {
5  console.log("Safe to use parsed.username and parsed.role.");
6} else {
7  throw new Error("Invalid payload");
8}
  • לוולידציה של קלט (ולידציית סכימה), שימוש בספריות כמו ajv מהווה פתרון עמיד.

שיקולי ביצועים

סיריאליזציה ודסיריאליזציה תכופות של אובייקטים גדולים מכבידות על ה-CPU והזיכרון. במידת הצורך, שקול לשלוח רק את ההבדלים, להשתמש בפורמטים בינאריים (כמו MessagePack), או לבצע סטרימינג (עיבוד נתונים רציף). בסביבות דפדפן, לעיתים ניתן להשתמש ב-structuredClone (להעתקה) או באובייקטים ניתנים להעברה ב-postMessage.

המלצות ממוקדות (בקצרה)

ניתן גם לשקול את הנקודות הבאות:.

  • בלוגים, העדף שלא להשתמש ב-pretty-print והשתמש ב-JSON בשורה אחת כדי להקטין את הגודל.
  • אובייקטים שעוברים סיריאליזציה תכופה, עדיף להקל עליהם מראש, למשל באמצעות הסרת מאפיינים שאינם נחוצים.
  • השתמש ב-replacer כדי לצמצם את המעבר בתכונות ולהחריג תכונות מיותרות.

דוגמה מעשית: זרם שליחת/קבלת בקשה ב-API

לבסוף, הנה רצף המדגים כיצד להמיר אובייקט ל-JSON בצורה בטוחה למשלוח לשרת וכיצד לשחזר תאריך מהתגובה שהתקבלה.

 1// Prepare payload, stringify safely, send via fetch, and revive dates on response.
 2async function sendUserUpdate(url, user) {
 3  // Remove sensitive info before sending
 4  const payload = JSON.stringify(user, (k, v) => (k === "password" ? undefined : v));
 5  const res = await fetch(url, {
 6    method: "POST",
 7    headers: { "Content-Type": "application/json" },
 8    body: payload
 9  });
10
11  const text = await res.text();
12  // Reviver: convert ISO date strings back to Date
13  const data = JSON.parse(text, (key, value) => {
14    if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
15      return new Date(value);
16    }
17    return value;
18  });
19  return data;
20}
  • בדוגמה הזאת, password לא נשלח, והתאריך משוחזר באמצעות reviver לאחר הקבלה. בעבודה בפועל, יש להוסיף טיפול בשגיאות ועיבוד טיימאאוטים.

סיכום

באמצעות JSON.stringify() ו-JSON.parse(), ניתן להמיר בין אובייקטים למחרוזות. ניתן להתאים את תהליך ההמרה באמצעות replacer ו-reviver, ואם מממשים toJSON במחלקה, ניתן לשלוט גם בכל אובייקט בנפרד. הפניות מעגליות, BigInt, פונקציות וכדומה אינם נתמכים ישירות, לכן יש לעבד אותם מראש.

תמיד אמת קלט ממקורות חיצוניים וודא שמידע רגיש אינו כלול בלוגים או בתקשורת. אם נפח הנתונים גדול, יעיל להימנע מהדפסה נאה, לשקול העברת הבדלים בלבד או להשתמש בפורמטים בינאריים.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video