คลาส `JSON` ใน JavaScript
บทความนี้อธิบายเกี่ยวกับคลาส JSON ใน JavaScript
เราจะอธิบายคลาส JSON ใน JavaScript พร้อมตัวอย่างที่ใช้งานจริง
YouTube Video
คลาส JSON ใน JavaScript
อ็อบเจกต์ JSON มีเมธอดหลักอยู่สองเมธอด JSON.stringify() แปลงอ็อบเจกต์เป็นสตริง JSON และ JSON.parse() สร้างอ็อบเจกต์จากสตริง JSON JSON เป็นรูปแบบการแลกเปลี่ยนข้อมูลที่สามารถแสดงเฉพาะค่าบางอย่างของ JavaScript เท่านั้น ฟังก์ชัน, undefined, Symbol และการอ้างอิงแบบวนซ้ำ (circular reference) ไม่สามารถแปลงเป็น 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คืนค่าสตริงแบบ synchronous
อาร์กิวเมนต์ตัวที่สองของ JSON.stringify (replacer)
โดยการระบุ replacer ซึ่งเป็นอาร์กิวเมนต์ที่สองของ JSON.stringify คุณสามารถควบคุมกฎการแปลงได้อย่างละเอียด นี่คือตัวอย่างที่ใช้ array เพื่อเก็บเฉพาะพร็อพเพอร์ตี้ที่ต้องการเท่านั้น
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จะถูกละเว้นเพื่อให้สามารถแสดง log ได้อย่างปลอดภัย สิ่งนี้มีประโยชน์เมื่อคุณต้องการตัดข้อมูลสำคัญออกด้วยเหตุผลด้านความปลอดภัย
การใช้ replacer เป็นฟังก์ชัน (สำหรับการแปลงค่าและคัดกรองข้อมูล)
หากคุณต้องการความยืดหยุ่นมากขึ้น คุณสามารถกำหนดฟังก์ชันเพื่อตรวจสอบแต่ละ key และ value ได้ ด้านล่างนี้เป็นตัวอย่างการเก็บ 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ที่เป็นฟังก์ชัน key และ value จะถูกส่งเข้าไป และค่าที่ return จะถูกใช้ในการสร้าง 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}- หากสตริงไม่ถูกต้อง จะเกิด exception ขึ้น ดังนั้นควรมีการจัดการข้อผิดพลาดเสมอเมื่อรับข้อมูลจากภายนอก
การเรียกคืนข้อมูลแบบกำหนดเองโดยใช้ reviver (เช่น แปลง string กลับเป็น 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จะถูกเรียกใช้กับแต่ละ key และค่าที่ return จะถูกใช้ในอ็อบเจกต์สุดท้าย โปรดตรวจสอบรูปแบบวันที่อย่างเข้มงวดตามที่แอปพลิเคชันของคุณต้องการ
การจัดรูปแบบสวยงาม (pretty-print) และ argument space
เพื่อให้อ่านง่ายขึ้น ให้ส่งตัวเลขหรือสตริงเป็น argument ตัวที่สามให้กับ 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 ที่ออกแบบมาเพื่อให้คนอ่าน เช่น log หรือไฟล์ config ควรกำหนดการเยื้องไว้เพื่อให้อ่านง่ายขึ้น แต่อย่าลืมว่าสิ่งนี้จะเพิ่มขนาดของข้อมูล
วิธีจัดการกับการอ้างอิงแบบวนซ้ำ (circular references)
JSON.stringify จะ throw TypeError ถ้ามี circular reference วิธีที่นิยมคือสร้าง replacer โดยใช้ set seen เพื่อหลีกเลี่ยง circular references
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 references ได้และลดปัญหา memory leak ในกรณีที่พบ circular reference จะคืนค่าเป็น"[Circular]"คุณยังสามารถระบุ reference ID แทนที่จะใช้"[Circular]"ได้เช่นกัน
การซีเรียลไลซ์แบบกำหนดเองด้วยเมธอด toJSON
หากคุณกำหนดเมธอด toJSON ในอ็อบเจกต์ JSON.stringify จะใช้ค่าที่ return มา สิ่งนี้มีประโยชน์เมื่อคุณต้องการกำหนดกฎการแปลงเฉพาะสำหรับแต่ละประเภท
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 floating-point)
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 แต่ก็ยังอันตรายหากคุณใช้อ็อบเจกต์ที่ได้โดยตรงโดยไม่ตรวจสอบก่อน ควรตรวจสอบและยืนยันเสมอว่าข้อมูลตรงตาม schema ที่คาดหวังไว้
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}- สำหรับการตรวจสอบอินพุต (schema validation) ควรใช้ไลบรารีเช่น
ajvเพื่อความมั่นคง
ข้อควรพิจารณาด้านประสิทธิภาพ
การซีเรียลไลซ์และดีซีเรียลไลซ์อ็อบเจกต์ขนาดใหญ่อย่างต่อเนื่องจะเพิ่มภาระให้กับ CPU และหน่วยความจำ หากจำเป็น คุณสามารถพิจารณาส่งเฉพาะส่วนที่แตกต่าง ใช้รูปแบบไบนารี (เช่น MessagePack) หรือสตรีม (ประมวลผลข้อมูลทีละส่วน) ในสภาพแวดล้อมเบราว์เซอร์ คุณสามารถใช้ structuredClone (สำหรับการคัดลอก) หรืออ็อบเจกต์ที่โอนได้ใน postMessage
คำแนะนำเฉพาะ (สั้น)
คุณสามารถพิจารณาประเด็นเหล่านี้เพิ่มเติมได้:
- สำหรับ log ควรหลีกเลี่ยง 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หลังจากได้รับข้อมูล ในการใช้งานจริง คุณควรเสริมการจัดการข้อผิดพลาดและการจัดการ timeout ด้วย
สรุป
ด้วย JSON.stringify() และ JSON.parse() คุณสามารถแปลงข้อมูลระหว่างอ็อบเจกต์และสตริงได้ คุณสามารถปรับแต่งกระบวนการแปลงข้อมูลโดยใช้ replacer และ reviver และถ้าคุณกำหนด toJSON ในคลาส ก็จะสามารถควบคุมอ็อบเจกต์แต่ละอันได้เป็นรายกรณี การอ้างอิงแบบวนซ้ำ, BigInt, ฟังก์ชัน ฯลฯ ไม่สามารถจัดการโดยตรงได้ คุณควรแปลงหรือจัดการไว้ล่วงหน้า
ควรตรวจสอบข้อมูลอินพุตเสมอ และตรวจสอบให้แน่ใจว่าไม่มีข้อมูลสำคัญถูกบันทึกลงใน log หรือส่งออกไป หากข้อมูลมีขนาดใหญ่ ควรหลีกเลี่ยง pretty-print ใช้การส่งเฉพาะที่แตกต่าง หรือใช้ฟอร์แมตแบบไบนารีเพื่อเพิ่มประสิทธิภาพ
คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย