JavaScript
Built-in Classes
JSON
ใช้ JSON เป็น data format สำหรับ serialize และแลกเปลี่ยนข้อมูล — JSON.stringify() แปลง JavaScript → string — JSON.parse() แปลง string → JavaScript
JSON คืออะไร — data format ที่แลกเปลี่ยนข้อมูลระหว่างระบบ
คุณเคยสงสัยไหมว่า API ส่งข้อมูลกลับมาให้เราได้ยังไง? หรือ localStorage เก็บ object ได้ยังไงในเมื่อมันเก็บได้แต่ string? **JSON** (JavaScript Object Notation — อ่านว่า "เจสัน") คือ **data format แบบ text** ที่ใช้แทนข้อมูลที่มีโครงสร้าง — มันเป็น string ธรรมดาที่เขียนตาม syntax ของ JavaScript object/array JSON ไม่ใช่ JavaScript object — มันคือ **string** ที่หน้าตาเหมือน object — และเป็น format มาตรฐานที่ใช้แลกเปลี่ยนข้อมูลระหว่างระบบต่าง ๆ ไม่ว่าจะเป็น frontend ↔ backend, config file, หรือ localStorage JSON ใช้เมื่อไหร่: - ส่งข้อมูลจาก server มาให้ browser — REST API, WebSocket - เก็บข้อมูลใน localStorage — เพราะ localStorage เก็บได้แต่ string - เก็บ config หรือ settings ในไฟล์ `.json` - deep clone object แบบง่าย — `JSON.parse(JSON.stringify(obj))` (แต่มีข้อจำกัด) JSON หน้าเป็นยังไง: - property name ต้องอยู่ใน double quote (`"name"`) - string ต้องใช้ double quote เท่านั้น (`"hello"`) — single quote ใช้ไม่ได้ - ค่าที่ใช้ได้: string, number, boolean, null, array, object - ค่าที่ใช้ไม่ได้: undefined, function, Symbol, Infinity, NaN, Date (จะถูกแปลงเป็น string)
// === JavaScript object ===
const obj = {
name: "Mai",
age: 25,
skills: ["js", "css"],
};
// === JSON string — หน้าตาเหมือนแต่คือ string ===
const json = `{"name":"Mai","age":25,"skills":["js","css"]}`;
console.log(typeof obj); // "object"
console.log(typeof json); // "string"
// JSON ใช้ double quote เท่านั้น — แบบนี้ผิด:
// '{ "name": "Mai" }' ✅
// "{ 'name': 'Mai' }" ❌ single quote ใช้ไม่ได้- JSON คือ data format แบบ text — ไม่ใช่ JavaScript object — มันคือ string
- JSON ใช้แลกเปลี่ยนข้อมูลระหว่างระบบ — API, config, localStorage
- JSON property name ต้องอยู่ใน double quote — string ใน JSON ก็ต้อง double quote
- JSON รองรับ 6 data type: string, number, boolean, null, array, object
- JSON ไม่รองรับ: undefined, function, Symbol, Infinity, NaN, Date (ถูกแปลงเป็น string)
JSON.stringify() — แปลง JavaScript → JSON string
**`JSON.stringify(value)`** คือ method ที่แปลง JavaScript value ให้เป็น JSON string วิธีใช้พื้นฐาน: ```js JSON.stringify(value) ``` - ส่งค่าอะไรก็ได้ — object, array, number, string, boolean, null - คืนค่าเป็น **string** ที่เป็น JSON format - ใช้ได้กับ nested object — `{ user: { name: "Mai" } }` ก็ทำงานได้ - array ก็แปลงได้ — `["a", "b"]` → `'["a","b"]'` - primitive ก็แปลงได้ — `JSON.stringify(42)` → `"42"` (เป็น string) จุดประสงค์หลักของ `JSON.stringify()` คือการ **serialize** — แปลงข้อมูลใน memory ให้เป็น string ที่ส่งต่อหรือบันทึกได้
// === Object → JSON string ===
const user = { name: "Mai", age: 25 };
const json = JSON.stringify(user);
console.log(json); // '{"name":"Mai","age":25}'
console.log(typeof json); // "string"
// === Array → JSON string ===
const arr = [1, 2, 3];
console.log(JSON.stringify(arr)); // '[1,2,3]'
// === Nested object ===
const order = {
id: 1001,
items: [
{ name: "book", price: 299 },
{ name: "pen", price: 49 },
],
};
console.log(JSON.stringify(order));
// '{"id":1001,"items":[{"name":"book","price":299},{"name":"pen","price":49}]}'
// === Primitive ===
console.log(JSON.stringify(42)); // "42"
console.log(JSON.stringify("hi")); // '"hi"'
console.log(JSON.stringify(true)); // "true"
console.log(JSON.stringify(null)); // "null"- `JSON.stringify(value)` แปลง JavaScript value → JSON string — คืนค่าเป็น string เสมอ
- ใช้กับ object, array, nested object, และ primitive ได้ทั้งหมด
- nested object ถูกแปลงทุกเลเวล — property ทุกตัวกลายเป็น JSON
- ผลลัพธ์คือ **string** — ตรวจสอบด้วย `typeof` จะได้ `"string"`
กฎการแปลงของ JSON.stringify — ค่าที่เปลี่ยนรูปและค่าที่หายไป
`JSON.stringify()` ไม่ได้เก็บทุกค่าแบบตรงไปตรงมา — มีกฎการแปลงที่คุณต้องรู้: **ค่าที่หายไป (ถูกละทิ้ง):** - `undefined` — property ที่มีค่าเป็น undefined จะ **หายไป** จาก JSON - `function` — method หรือ function property จะ **หายไป** - `Symbol` — property ที่ key เป็น Symbol จะ **หายไป** **ค่าที่เปลี่ยนรูป:** - `NaN` → กลายเป็น `null` - `Infinity` / `-Infinity` → กลายเป็น `null` - `Date` object → กลายเป็น ISO string (เช่น `"2025-01-15T00:00:00.000Z"`) - `BigInt` → **throw TypeError** — แปลงไม่ได้! **กฎพิเศษ:** - ถ้า object มี method ชื่อ `toJSON()` — `JSON.stringify()` จะเรียก `toJSON()` และใช้ค่าที่คืนกลับมาแทน - **circular reference** (object อ้างอิงตัวเองเป็นวง) — `JSON.stringify()` จะ **throw TypeError**
const data = {
name: "Mai",
age: undefined, // จะหายไป
greet: function() {}, // จะหายไป
score: NaN, // กลายเป็น null
max: Infinity, // กลายเป็น null
createdAt: new Date(),// กลายเป็น ISO string
};
console.log(JSON.stringify(data));
// {"name":"Mai","score":null,"max":null,"createdAt":"2025-01-15T..."}
// สังเกต: age และ greet หายไป — score/max เป็น null
// === toJSON() — ควบคุมการแปลง ===
const product = {
name: "Laptop",
price: 29900,
toJSON() {
return { name: this.name }; // ส่งคืนเฉพาะ name
},
};
console.log(JSON.stringify(product)); // '{"name":"Laptop"}'
// price หายเพราะ toJSON() ไม่ได้รวมไว้
// === Circular reference — throw error ===
const a = { name: "A" };
const b = { name: "B", ref: a };
a.ref = b; // a → b → a → b → ... วงไม่สิ้นสุด
try {
JSON.stringify(a);
} catch (e) {
console.log(e.message); // "Converting circular structure to JSON"
}| ค่าใน JavaScript | ผลลัพธ์ใน JSON | หมายเหตุ |
|---|---|---|
| `undefined` | หายไป | property ถูกข้าม — ใน array กลายเป็น `null` |
| `function` | หายไป | method/property ที่เป็น function ถูกละทิ้ง |
| `Symbol` | หายไป | key ที่เป็น Symbol และ value ที่เป็น Symbol หาย |
| `NaN` | `null` | เปลี่ยนเป็น `null` — ไม่ใช่ `"NaN"` |
| `Infinity` / `-Infinity` | `null` | เปลี่ยนเป็น `null` |
| `Date` | ISO string | เช่น `"2025-01-15T00:00:00.000Z"` |
| `BigInt` | throw TypeError | แปลงไม่ได้ — ต้องแปลงเป็น number/string ก่อน |
| `toJSON()` | ค่าจาก `toJSON()` | ถ้ามี method นี้ — ใช้ค่าที่คืนกลับมา |
| circular reference | throw TypeError | object อ้างอิงเป็นวง — แปลงไม่ได้ |
- `undefined`, `function`, `Symbol` — หายไปจาก JSON — ไม่ถูกเก็บ
- `NaN`, `Infinity` — กลายเป็น `null` — ไม่ใช่ค่าตัวเลขตามที่เห็นใน JavaScript
- `Date` — กลายเป็น ISO string — เมื่อ parse กลับจะไม่ใช่ Date object (เป็น string)
- `toJSON()` — ควบคุมได้ว่าอยากให้ `JSON.stringify()` เก็บอะไร — คล้าย custom serializer
- circular reference — `JSON.stringify()` throw error — ต้องแก้โครงสร้างข้อมูลก่อน
JSON.stringify() กับ space — กำหนด indentation ให้อ่านง่าย
ปกติ `JSON.stringify()` จะคืนค่า string แบบ compact — ทุกอย่างต่อกันบรรทัดเดียว — อ่านยากมากเมื่อ object ใหญ่ **space parameter** คือ argument ตัวที่ 3 ของ `JSON.stringify()`: ```js JSON.stringify(value, null, space) ``` - `space` เป็น **number** — จำนวนช่องว่างสำหรับ indentation ในแต่ละระดับ (ใช้ 2 หรือ 4 บ่อยที่สุด) - `space` เป็น **string** — ใช้ string นั้นเป็น indent (เช่น `" "` หรือ `"\t"`) - `space` เป็น `0` หรือไม่ระบุ — ไม่มี whitespace (compact) - `space` เกิน 10 — จะถูกลดเหลือ 10 ใช้เมื่อไหร่: - debug — ดูโครงสร้าง JSON ให้อ่านง่าย - เก็บ config ที่มนุษย์ต้องแก้ไข - log ข้อมูลไว้ในไฟล์
const user = {
name: "Mai",
age: 25,
skills: ["js", "react", "node"],
address: { city: "Bangkok", zip: "10110" },
};
// === ไม่มี space — compact ===
console.log(JSON.stringify(user));
// '{"name":"Mai","age":25,"skills":["js","react","node"],"address":{"city":"Bangkok","zip":"10110"}}'
// === space = 2 — อ่านง่าย ===
console.log(JSON.stringify(user, null, 2));
/* {
"name": "Mai",
"age": 25,
"skills": [
"js",
"react",
"node"
],
"address": {
"city": "Bangkok",
"zip": "10110"
}
} */
// === space = "→ " (string indent) ===
console.log(JSON.stringify(user, null, "→ "));
/* {
→ "name": "Mai",
→ "age": 25,
→ ...
} */- `space` เป็น number — จำนวนช่องว่างต่อระดับ — ใช้ 2 หรือ 4 บ่อยที่สุด
- `space` เป็น string — ใช้ string นั้นเป็น indent (สูงสุด 10 ตัวอักษรแรกของ string)
- `space` = 0 หรือไม่ระบุ → compact — space > 10 → ถูก clamp เหลือ 10
- ใช้ `space` เมื่อต้องการให้มนุษย์อ่าน — debug, config, log file
JSON.stringify() กับ replacer — เลือกเฉพาะ key ที่ต้องการ
**replacer** คือ argument ตัวที่ 2 ของ `JSON.stringify()` — ใช้ **กรองหรือแปลง** property ก่อน serialize ```js JSON.stringify(value, replacer, space) ``` replacer มี 2 รูปแบบ: **1. replacer เป็น array ของ string** — ระบุชื่อ property ที่ต้องการเก็บ — property ที่ชื่อไม่อยู่ใน array จะถูกข้าม ```js JSON.stringify(user, ["name", "age"]) // เก็บเฉพาะ name และ age — property อื่นหายหมด ``` **2. replacer เป็น function** — ถูกเรียกกับทุก key-value — คืนค่าที่ต้องการเก็บ หรือคืน `undefined` เพื่อข้าม ```js JSON.stringify(user, (key, value) => { if (key === "password") return undefined; // ข้าม password return value; // เก็บค่าอื่นตามปกติ }) ``` function replacer รับ `(key, value)`: - ครั้งแรก `key` เป็น `""` (empty string) และ `value` เป็น object ทั้งก้อน - ครั้งต่อ ๆ ไป `key` เป็นชื่อ property และ `value` เป็นค่าของ property นั้น ใช้เมื่อไหร่: - ซ่อน sensitive data (password, token) ก่อนส่ง log หรือ API - เลือกเฉพาะฟิลด์ที่ต้องการ sync กับ server - แปลงค่าระหว่าง serialize (เช่น format วันที่)
const user = {
id: 1,
name: "Mai",
email: "mai@example.com",
password: "secret123",
role: "admin",
};
// === เก็บเฉพาะ name และ email ===
const filtered = JSON.stringify(user, ["name", "email"], 2);
console.log(filtered);
/* {
"name": "Mai",
"email": "mai@example.com"
} */
// id, password, role — หายหมด
// === ส่งไป API — เอาเฉพาะฟิลด์ที่ server ต้องการ ===
const payload = JSON.stringify(user, ["name", "email", "role"]);
console.log(payload);
// '{"name":"Mai","email":"mai@example.com","role":"admin"}'const data = {
name: "Mai",
password: "secret123",
token: "abc-xyz",
settings: { theme: "dark", notify: true },
};
// === ตัด password และ token ออก — อย่างอื่นเก็บตามปกติ ===
const safe = JSON.stringify(data, (key, value) => {
if (key === "password" || key === "token") {
return undefined; // ข้าม — ไม่เก็บลง JSON
}
return value; // เก็บค่าอื่นตามปกติ
}, 2);
console.log(safe);
/* {
"name": "Mai",
"settings": {
"theme": "dark",
"notify": true
}
} */
// password และ token หายไป
// === แปลงค่าระหว่าง serialize ===
const withDate = {
event: "workshop",
date: new Date("2025-06-15"),
};
const formatted = JSON.stringify(withDate, (key, value) => {
if (key === "date" && value instanceof Date) {
return value.toLocaleDateString("th-TH"); // "15/6/2568"
}
return value;
}, 2);
console.log(formatted);
/* {
"event": "workshop",
"date": "15/6/2568"
} */- replacer array — ระบุ white-list ของ property ที่ต้องการเก็บ — property อื่นหาย
- replacer function — เรียกกับทุก key-value — คืน `undefined` เพื่อข้าม — คืนค่าอื่นเพื่อเก็บ
- replacer function ถูกเรียกครั้งแรกด้วย `key: ""` และ `value` เป็น object ทั้งก้อน
- ใช้ replacer เมื่อต้องการกรอง sensitive data, เลือกฟิลด์, หรือแปลงค่าระหว่าง serialize
JSON.parse() — แปลง JSON string → JavaScript value
**`JSON.parse(text)`** คือ method ที่ทำตรงข้ามกับ `JSON.stringify()` — แปลง JSON string กลับเป็น JavaScript value ```js JSON.parse(text) ``` - รับ JSON string — คืนค่า JavaScript (object, array, string, number, boolean, หรือ null) - JSON ต้องเป็น **valid JSON** — ถ้า format ผิดจะ **throw SyntaxError** - string ใน JSON ต้องใช้ double quote — single quote ใช้ไม่ได้ - property name ต้องอยู่ใน double quote — `{ name: "Mai" }` (ไม่มี quote ครอบ name) → error วิธีใช้ที่พบบ่อย: - **parse API response** — `fetch().then(res => res.json())` ภายใต้ hood ก็คือ `JSON.parse()` - **อ่านข้อมูลจาก localStorage** — `JSON.parse(localStorage.getItem("key"))` - **อ่าน config จากไฟล์** — require/import `.json` ก็ผ่าน `JSON.parse()` ข้อควรระวัง: - `JSON.parse()` ไม่รับ trailing comma — `{"a":1,}` → error - ต้องเป็น JSON จริง ๆ — object literal ของ JavaScript (`{a:1}`) ใช้ไม่ได้
// === Object JSON → JavaScript object ===
const json = '{"name":"Mai","age":25}';
const obj = JSON.parse(json);
console.log(obj.name); // "Mai"
console.log(obj.age); // 25
console.log(typeof obj); // "object"
// === Array JSON → JavaScript array ===
const arr = JSON.parse('[1, 2, 3]');
console.log(arr); // [1, 2, 3]
console.log(Array.isArray(arr)); // true
// === Nested JSON ===
const nested = JSON.parse(
'{"user":{"name":"Mai"},"tags":["js","css"]}'
);
console.log(nested.user.name); // "Mai"
console.log(nested.tags[0]); // "js"
// === JSON.parse() throw error เมื่อ format ผิด ===
try {
JSON.parse("{ name: 'Mai' }"); // ❌ ไม่มี double quote ครอบ name
} catch (e) {
console.log("Error:", e.message);
// "Expected property name or '}' in JSON at position 2"
}
try {
JSON.parse('{"a":1,}'); // ❌ trailing comma
} catch (e) {
console.log("Error:", e.message);
}
// === Deep clone ด้วย JSON ===
const original = { a: 1, b: { c: 2 } };
const clone = JSON.parse(JSON.stringify(original));
console.log(clone.b.c); // 2
clone.b.c = 99;
console.log(original.b.c); // 2 — ไม่เปลี่ยน (คนละ reference)- `JSON.parse(text)` แปลง JSON string → JavaScript value — ตรงข้ามกับ `JSON.stringify()`
- JSON string ต้อง valid — double quote ครอบ property name และ string — ไม่มี trailing comma
- format ผิด → throw `SyntaxError` — ควรใช้ `try...catch` เมื่อ parse ข้อมูลจากภายนอก
- deep clone แบบง่าย: `JSON.parse(JSON.stringify(obj))` — แต่มีข้อจำกัด (ค่า undefined/function/Date หาย)
- `fetch().then(r => r.json())` — `response.json()` ก็ใช้ `JSON.parse()` ข้างใน
JSON.parse() กับ reviver — แปลงค่าระหว่าง parse
**reviver** คือ argument ตัวที่ 2 ของ `JSON.parse()` — ใช้ **แปลงค่า** ระหว่างการ parse ก่อนคืนเป็น JavaScript value ```js JSON.parse(text, reviver) ``` reviver เป็น function ที่รับ `(key, value)` — คล้ายกับ replacer ของ `JSON.stringify()` แต่ทำงานในทิศทางตรงข้าม (parse แทน serialize): - ถูกเรียกกับทุก key-value **จากล่างขึ้นบน** (leaf node ก่อน root) - คืนค่าที่ต้องการ — หรือคืน `undefined` เพื่อลบค่านั้นออก - ครั้งสุดท้าย key เป็น `""` และ value เป็น object ทั้งก้อน ใช้ reviver เมื่อไหร่: - **แปลง date string → Date object** — เพราะ JSON ไม่มี type Date — `JSON.parse()` ได้ string - **แปลง number string → number** — เมื่อข้อมูลต้นทางส่งมาเป็น string - **กรองค่าที่ไม่ต้องการ** — เช่น ลบ property ที่เป็น `null` - **แปลงรูปแบบข้อมูล** — เช่น map key name จาก snake_case → camelCase
const json = `{
"event": "workshop",
"createdAt": "2025-03-15T09:00:00.000Z",
"updatedAt": "2025-04-01T14:30:00.000Z",
"attendees": 120
}`;
// === ไม่มี reviver — date string ก็คือ string ===
const raw = JSON.parse(json);
console.log(raw.createdAt); // "2025-03-15T09:00:00.000Z"
console.log(typeof raw.createdAt); // "string" — ไม่ใช่ Date!
// === ใช้ reviver — แปลง date string → Date object ===
const withDates = JSON.parse(json, (key, value) => {
// เช็กว่า key ลงท้ายด้วย "At" และ value เป็น string ที่ดูเหมือน ISO date
if (key.endsWith("At") && typeof value === "string") {
const date = new Date(value);
if (!isNaN(date.getTime())) return date;
}
return value;
});
console.log(withDates.createdAt); // Date object
console.log(withDates.createdAt instanceof Date); // true
console.log(withDates.createdAt.getFullYear()); // 2025
console.log(withDates.updatedAt.getMonth()); // 3 (April = เดือนที่ 3)const json = `{
"user_id": 1,
"first_name": "Mai",
"last_name": "Riam",
"email_address": "mai@example.com"
}`;
function snakeToCamel(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
const result = JSON.parse(json, (key, value) => {
// key ที่มี _ → แปลงเป็น camelCase
if (key.includes("_")) {
// วิธีนี้ใช้ reviver กับ object — สร้าง key ใหม่
// แต่ reviver ธรรมดาเปลี่ยน key ไม่ได้ — ต้องใช้เทคนิคอื่น
}
return value;
});
// reviver เปลี่ยน key โดยตรงไม่ได้ — แต่วิธีเหนือใช้ได้:
// 1. JSON.parse() ได้ object
// 2. เปลี่ยน key ด้วย Object.entries() + reduce()
const convertKeys = (obj) =>
Object.fromEntries(
Object.entries(obj).map(([key, value]) => [snakeToCamel(key), value])
);
const camelCase = convertKeys(JSON.parse(json));
console.log(camelCase);
// { userId: 1, firstName: "Mai", lastName: "Riam", emailAddress: "mai@example.com" }| สถานการณ์ | ใช้ reviver อย่างไร | ตัวอย่าง |
|---|---|---|
| date string → Date | เช็ก key และแปลง `new Date(value)` | `(k,v) => k.endsWith('At') ? new Date(v) : v` |
| ลบ property ที่เป็น null | คืน `undefined` เมื่อ value เป็น null | `(k,v) => v === null ? undefined : v` |
| แปลงตัวเลขที่เป็น string → number | เช็ก `typeof value === 'string'` และไม่ใช่ NaN | `(k,v) => !isNaN(+v) && typeof v==='string' ? +v : v` |
| เปลี่ยน key name (snake→camel) | reviver เปลี่ยน key โดยตรงไม่ได้ — ใช้ parse แล้ว Object.entries() แทน | parse ก่อนแล้ว map key ด้วยฟังก์ชันแยก |
- reviver ถูกเรียก **จากล่างขึ้นบน** — leaf node ก่อน root — คล้ายการเดินทางกลับของ recursion
- reviver คืน `undefined` เพื่อลบค่านั้น — ใช้กรองค่าที่ไม่ต้องการ
- ใช้ reviver แปลง date string → Date object — เพราะ `JSON.parse()` ได้ string อย่างเดียว
- reviver เปลี่ยน key name โดยตรงไม่ได้ — ต้อง parse ก่อนแล้วเปลี่ยน key ใน JavaScript
ข้อควรระวังกับ JSON — เรื่องที่มักพลาด
JSON เรียบง่ายแต่มีจุดที่พลาดได้ง่าย — โดยเฉพาะเมื่อใช้ `JSON.stringify()` / `JSON.parse()` ในงานจริง **5 เรื่องที่มักพลาด:** 1. **Deep clone ด้วย JSON เสียข้อมูล** — `JSON.parse(JSON.stringify(obj))` clone ได้เร็ว แต่ undefined, function, Date, Map, Set จะหายหรือเสียรูป — ใช้ `structuredClone()` แทนเมื่อต้องการ clone สมบูรณ์ 2. **Date ไม่กลับมาเป็น Date** — `JSON.stringify()` แปลง Date → ISO string — แต่ `JSON.parse()` คืน string ไม่ใช่ Date — ต้องใช้ reviver แปลงกลับ 3. **undefined ใน array กลายเป็น null** — `JSON.stringify([1, undefined, 3])` → `"[1,null,3]"` — undefined ใน object property หาย แต่ใน array กลายเป็น null 4. **JSON.parse() กับข้อมูลจากภายนอก** — ข้อมูลจาก API, user input, หรือ localStorage อาจไม่ใช่ valid JSON — ควรใช้ `try...catch` เสมอ 5. **BigInt แปลงไม่ได้** — `JSON.stringify(1n)` → throw `TypeError` — ต้องแปลงเป็น number/string ก่อน — และเมื่อ parse กลับก็ไม่ใช่ BigInt
const original = {
name: "Mai",
created: new Date(),
greet() { return "hi"; },
tag: undefined,
reg: /test/,
map: new Map([["key", "value"]]),
};
const clone = JSON.parse(JSON.stringify(original));
console.log(clone.name); // "Mai" ✅
console.log(clone.created); // "2025-..." (string, ไม่ใช่ Date) ❌
console.log(clone.greet); // undefined (หายไป) ❌
console.log(clone.tag); // undefined (หายไป) ❌
console.log(clone.reg); // {} (empty object) ❌
console.log(clone.map); // {} (empty object) ❌
// === ใช้ structuredClone() แทน ===
const betterClone = structuredClone(original);
console.log(betterClone.created instanceof Date); // true ✅
console.log(betterClone.map instanceof Map); // true ✅// === ข้อมูลจาก localStorage — อาจไม่ใช่ JSON ===
function safeGetFromStorage(key) {
try {
const raw = localStorage.getItem(key);
return raw ? JSON.parse(raw) : null;
} catch {
console.warn(`Failed to parse "${key}" from storage`);
return null;
}
}
// === ข้อมูลจาก API — response.json() throw เมื่อ body ไม่ใช่ JSON ===
async function safeFetchJSON(url) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json(); // json() throw ถ้า body ไม่ใช่ valid JSON
} catch (err) {
console.error("Failed to fetch/parse:", err.message);
return null;
}
}
// === undefined ใน array vs object ===
console.log(JSON.stringify([1, undefined, 3])); // "[1,null,3]"
console.log(JSON.stringify({ a: 1, b: undefined, c: 3 })); // '{"a":1,"c":3}'
// array: undefined → null
// object: undefined → property หาย| ข้อผิดพลาด | สาเหตุ | วิธีแก้ |
|---|---|---|
| deep clone แล้ว Date/Map/Set เสีย | JSON ไม่รู้จัก type เหล่านี้ — `JSON.stringify()` แปลงเป็น string/empty object | ใช้ `structuredClone()` ใน environment ที่รองรับ — หรือใช้ lodash.cloneDeep |
| `JSON.parse()` throw `SyntaxError` | JSON string ไม่ valid — single quote, trailing comma, หรือ property name ไม่มี double quote | ใช้ `try...catch` เสมอเมื่อ parse ข้อมูลจากภายนอก |
| undefined ใน array กลายเป็น `null` | JSON ไม่รู้จัก undefined — ใน array ใช้ `null` แทน — ใน object หายไปเลย | ระวังเมื่อ serialize array ที่มี sparse element |
| Date ที่ parse กลับมาเป็น string | `JSON.parse()` ไม่รู้ว่าค่าไหนเป็น Date — คืน string ตามที่เห็นใน JSON | ใช้ reviver ใน `JSON.parse()` เพื่อแปลง date string กลับเป็น Date |
| `JSON.stringify()` throw เมื่อเจอ BigInt | JSON spec ไม่รองรับ BigInt — `JSON.stringify()` throw TypeError | แปลงเป็น number หรือ string ก่อน — `BigInt(123).toString()` |
- `JSON.parse(JSON.stringify(obj))` deep clone ได้เร็ว — แต่ undefined, function, Date, Map, Set หายหรือเสีย
- Date ถูก serialize เป็น string — ต้องใช้ reviver แปลงกลับเมื่อ parse
- ข้อมูลจากภายนอก (API, localStorage, user input) — ใช้ `try...catch` ครอบ `JSON.parse()` เสมอ
- `undefined` ใน array กลายเป็น `null` — ใน object property หาย — จำพฤติกรรมนี้ให้ดี
- BigInt และ Symbol — `JSON.stringify()` throw หรือข้าม — ต้องจัดการเองก่อน serialize