JavaScript 中的 `JSON` 類別
本文將說明 JavaScript 中的 JSON 類別。
我們會透過實際範例來說明 JavaScript 中的 JSON 類別。
YouTube Video
JavaScript 中的 JSON 類別
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)
透過指定 JSON.stringify 的第二個參數 replacer,可以細緻地控制轉換規則。以下例子說明如何僅保留特定屬性(透過陣列)。
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中,會傳入每個鍵與值,並以回傳值作為最終輸出的部分。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 還原為例)
透過 JSON.parse 的第二個參數 reviver,可以對值進行還原。以下例子說明如何將儲存的日期字串還原成 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會針對每個鍵呼叫,回傳值會用於最終物件。請依你的應用需求做嚴謹的日期格式檢查。
美化輸出與 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。常用的解法是用一個 seen 集合於 replacer 來避免循環參照。
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]"。也可以指定一個參照 ID 取代"[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 的可轉移物件。
具體建議(簡要)
還可以考慮以下幾點:。
- 針對日誌,請避免美化輸出,盡量以單行 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、函式等不能原樣處理,需事先轉換。
外部輸入請務必驗證,並確認敏感資訊未出現在日誌或傳輸資料中。若資料量龐大,建議避免美化輸出,可考慮差異傳輸或二進位格式。
您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。