7 เทคนิค JSON ที่จะช่วยคุณประหยัดเวลาเป็นชั่วโมง
นักพัฒนา JavaScript ทุกคนใช้ JSON.parse และ JSON.stringify หลายสิบครั้งต่อสัปดาห์ แต่ส่วนใหญ่หยุดอยู่ที่พื้นฐาน — การ parse API response และการ serialize object เป็นสตริง API มีมากกว่านั้น และฟีเจอร์ที่ใช้น้อยบางตัวแก้ปัญหาที่ไม่งั้นต้อง library บุคคลที่สามหรือเสียเวลาดีบักหลายชั่วโมง
คู่มือนี้ครอบคลุมเจ็ดเทคนิคที่ก้าวข้ามค่าเริ่มต้น แต่ละข้อใช้ได้กับ codebase จริงทันที ไม่มีนามธรรม ไม่มีตัวอย่างที่ประดิษฐ์ — นี่คือรูปแบบที่ปรากฏในระบบ production
คุณสามารถตรวจสอบและสำรวจตัวอย่างโค้ดใดๆ ในบทความนี้ได้ด้วย Toova JSON Formatter ซึ่งตรวจสอบและ pretty-print JSON ภายในเบราว์เซอร์ของคุณทั้งหมด
1. Deep Clone ด้วย JSON Round-Trip (และข้อจำกัด)
ทริกเก่าแก่ที่สุดในตำรา JavaScript: ใช้ JSON.parse(JSON.stringify(obj)) สร้าง deep clone ของ plain object
const original = { a: 1, b: { c: 2 } };
// วิธีแบบเบื้องต้น — ออบเจ็กต์ที่ซ้อนลึกยังถูกแชร์อยู่
const shallowCopy = { ...original }; // b ยังชี้ไปที่ object เดิม
// JSON deep clone — สร้างสำเนาที่อิสระจากต้นฉบับโดยสมบูรณ์
const deepClone = JSON.parse(JSON.stringify(original));
deepClone.b.c = 99;
console.log(original.b.c); // 2 — ต้นฉบับไม่ถูกแตะ วิธีนี้ใช้ได้เพราะการ serialize เป็นสตริงและ parse กลับสร้าง object tree ใหม่ทั้งหมดโดยไม่มี reference ที่แชร์กัน รวดเร็ว ไม่ต้องการ dependency และมีให้ใช้ตั้งแต่ ES5
ทางเลือกสมัยใหม่สำหรับการใช้ใน memory คือ structuredClone():
// ทางเลือกสมัยใหม่: structuredClone() — จัดการ type ได้มากกว่า
// รองรับใน Node.js 17+ และ browser แบบ evergreen ทุกตัว
const clone = structuredClone(original); รู้ข้อจำกัดของวิธี JSON ก่อนใช้:
// JSON.parse/stringify ไม่สามารถจัดการ:
const broken = {
date: new Date(), // กลายเป็นสตริง — เสีย Date prototype
fn: () => 'hello', // ถูกทิ้งแบบเงียบๆ
undef: undefined, // ถูกทิ้งแบบเงียบๆ
inf: Infinity, // กลายเป็น null
map: new Map(), // กลายเป็น {}
cycle: null, // circular ref จะ throw
};
หาก object ของคุณมีแต่ข้อมูลธรรมดา — สตริง, ตัวเลข, boolean, array และ plain object ซ้อน — JSON round-trip ปลอดภัยและรวดเร็ว สำหรับอย่างอื่น เลือก structuredClone() หรือ library เฉพาะ
2. Custom Replacer สำหรับการกรองและ Mask ค่า
นักพัฒนาส่วนใหญ่รู้ว่า JSON.stringify รับอาร์กิวเมนต์ที่สอง แต่ใช้น้อยมาก อาร์กิวเมนต์ที่สองนี้คือ replacer: ทั้ง array ของคีย์ที่จะรวม หรือ function ที่ควบคุมว่าค่าแต่ละตัวจะถูก serialize อย่างไรพอดี
Replacer แบบ array — whitelist คีย์เฉพาะ:
const user = {
id: 'u_001',
name: 'Alice',
password: 'hunter2', // ห้ามปรากฏใน log
creditCard: '4111111111111111', // ห้ามปรากฏใน log
role: 'admin',
};
// Replacer แบบ array: รวมเฉพาะคีย์เหล่านี้
JSON.stringify(user, ['id', 'name', 'role']);
// '{"id":"u_001","name":"Alice","role":"admin"}' Replacer แบบ function — แปลงหรือซ่อนค่า:
// Replacer แบบ function: ควบคุม key/value ได้เต็มที่
const masked = JSON.stringify(user, (key, value) => {
if (key === 'password' || key === 'creditCard') return '[REDACTED]';
if (key === '' ) return value; // root object — return เสมอ
return value;
});
// '{"id":"u_001","name":"Alice","password":"[REDACTED]","creditCard":"[REDACTED]","role":"admin"}' เวอร์ชันที่ซับซ้อนกว่าซ่อนค่าตามรูปร่างของมันมากกว่าชื่อคีย์:
// Replacer สำหรับการ mask ตามประเภทค่า
const sanitize = (key, value) => {
if (typeof value === 'string' && value.match(/^4[0-9]{15}$/)) {
return '****-****-****-' + value.slice(-4);
}
return value;
}; เทคนิคนี้ขาดไม่ได้สำหรับ logging middleware: คุณต้องการ log ที่มีโครงสร้างพร้อมบริบท object ครบ แต่บางฟิลด์ห้ามไปถึง log aggregator Replacer ให้คุณจัดการเรื่องนี้ที่ขอบเขต serialization แทนการกระจาย logic การซ่อนทั่ว codebase
3. เรียงคีย์แบบ Deterministic
ลำดับคีย์ของ object ใน JavaScript คือลำดับการ insert (สำหรับคีย์สตริง) Object สองตัวที่มีคีย์เดียวกันแต่สร้างในลำดับต่างกันสร้าง JSON สตริงต่างกัน ซึ่งทำให้การเช็คความเท่ากันแบบพื้นๆ, cache key และ content hash เสีย
function sortedStringify(obj) {
return JSON.stringify(obj, Object.keys(obj).sort());
}
const a = { z: 1, a: 2, m: 3 };
const b = { a: 2, m: 3, z: 1 };
sortedStringify(a) === sortedStringify(b); // true — ลำดับคีย์ถูกทำให้เป็นมาตรฐาน สำหรับ object ที่ซ้อนลึก ใช้การเรียงแบบ recursive:
// เรียงคีย์แบบ recursive สำหรับ object ซ้อน
function sortKeys(value) {
if (Array.isArray(value)) return value.map(sortKeys);
if (value !== null && typeof value === 'object') {
return Object.fromEntries(
Object.keys(value).sort().map((k) => [k, sortKeys(value[k])])
);
}
return value;
}
const sorted = JSON.stringify(sortKeys(deepNested)); JSON ที่เรียงแล้วจำเป็นเมื่อคุณ:
- สร้าง cache key จาก request body
- คำนวณ checksum หรือ signature บน JSON payload
- เปรียบเทียบ API response ในการทดสอบโดยไม่สนใจลำดับฟิลด์
- เก็บ object การตั้งค่าที่ลำดับไม่ควรกระทบความเท่ากัน
หลังจากเรียงและ pretty-print แล้ว ใช้ เครื่องมือ Text Diff เปรียบเทียบสตริง JSON ที่ทำให้เป็นมาตรฐานสองตัวและดูว่าค่าใดเปลี่ยนระหว่างเวอร์ชัน
4. จัดการ BigInt โดยไม่สูญเสียความแม่นยำ
Type Number ของ JavaScript สามารถแทนเลขจำนวนเต็มได้อย่างปลอดภัยจนถึง 253 − 1 สำหรับ ID ที่สร้างโดยระบบกระจาย, จำนวนเงินในหน่วยย่อย หรือ timestamp ในนาโนวินาที นี่ไม่พอ BigInt ครอบคลุมเลขจำนวนเต็มที่ความแม่นยำตามต้องการ แต่ JSON.stringify ไม่รู้จะทำอย่างไรกับมัน
const data = {
amount: 9007199254740993n, // ใหญ่กว่า Number.MAX_SAFE_INTEGER
};
// อันนี้ throw: TypeError: Do not know how to serialize a BigInt
JSON.stringify(data); // ERROR วิธีแก้มาตรฐาน:
// วิธี 1: แปลงเป็นสตริงด้วย replacer
JSON.stringify(data, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
);
// '{"amount":"9007199254740993"}'
// วิธี 2: toJSON() บน BigInt prototype (monkey-patch — ใช้ด้วยความระวัง)
BigInt.prototype.toJSON = function () { return this.toString(); };
JSON.stringify(data); // '{"amount":"9007199254740993"}' ฝั่ง parse, reviver function สามารถกู้ค่า BigInt จากการแทนแบบสตริงได้:
// Reviver เพื่อกู้ BigInt ตอน parse
const revived = JSON.parse('{"amount":"9007199254740993"}', (key, value) => {
if (key === 'amount') return BigInt(value);
return value;
});
console.log(typeof revived.amount); // 'bigint'
เก็บการแปลง BigInt-เป็น-สตริงในเลเยอร์ serialization ที่ใช้ร่วมเพื่อให้ใช้สม่ำเสมอ ปล่อยให้ BigInt หลุดไปการเรียก JSON.stringify ที่กระจัดกระจายตามขอบของ codebase นำไปสู่ข้อผิดพลาดที่ไม่สามารถคาดเดาได้และยากต่อการตามรอย
5. การตรวจจับ Circular Reference
Circular reference เกิดขึ้นเมื่อ object มี reference ถึงตัวเองหรือ ancestor ในกราฟ object พวกมันพบบ่อยกว่าที่คิด: event emitter, DOM node, React fiber node และ ORM entity มักมี back-reference
// ตัวอย่าง circular reference
const obj = { name: 'node' };
obj.self = obj; // obj อ้างถึงตัวเอง
JSON.stringify(obj); // throw: TypeError: Converting circular structure to JSON จัดการด้วย replacer แบบ custom ที่ติดตาม object ที่เยี่ยมแล้ว:
// ตัวจัดการ circular reference แบบทำเอง
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
});
}
safeStringify(obj); // '{"name":"node","self":"[Circular]"}' WeakSet คือโครงสร้างข้อมูลที่ถูกต้องที่นี่: ถือ object reference โดยไม่ป้องกันการ garbage collect และ lookup เป็น O(1) รูปแบบนี้ยังทำงานใน logging middleware ที่คุณต้องการให้ error degrade อย่างสง่างามแทนที่จะ throw ระหว่าง serialization
การแปรผันที่พบบ่อย: แทนการ mark circular reference เป็น [Circular] ให้แทนด้วย path string เช่น [Circular: $.config.parent] เพื่อให้ตำแหน่งของ reference ชัดเจนระหว่างการดีบัก
6. Pretty-Print Array บนบรรทัดเดียว
อาร์กิวเมนต์ที่สามของ JSON.stringify คือ indent ส่ง 2 หรือ 4 ขยายทุกอย่างเป็นหลายบรรทัด เหมาะมากสำหรับ object แต่อาจยาวเกินจำเป็นสำหรับ array ของ primitive เช่น tag, ID หรือ coordinate
const mixed = {
title: 'Report',
tags: ['json', 'api', 'debug'],
config: { indent: 2, sortKeys: true },
count: 42,
};
// ค่าเริ่มต้น: ทุกอย่างหลายบรรทัด
JSON.stringify(mixed, null, 2);
// {
// "title": "Report",
// "tags": [
// "json",
// "api",
// "debug"
// ],
// "config": {
// "indent": 2,
// "sortKeys": true
// },
// "count": 42
// } คุณสามารถ post-process output เพื่อยุบ array แบบง่ายลงเป็นบรรทัดเดียว:
// Replacer แบบ custom สำหรับ array บรรทัดเดียว
function prettyMixed(obj) {
const raw = JSON.stringify(obj, null, 2);
// ยุบ array ที่มีแต่ค่า primitive ลงเป็นบรรทัดเดียว
return raw.replace(
/\[\n\s+([\s\S]*?)\n\s+\]/g,
(match, inner) => {
const items = inner.split(',\n').map((s) => s.trim());
if (items.every((s) => !/^[{\[]/.test(s))) {
return '[' + items.join(', ') + ']';
}
return match;
}
);
} ผลลัพธ์: object ยังคงหลายบรรทัดเพื่อความอ่านง่าย array ของ primitive ยุบเป็นบรรทัดเดียวเพื่อความกระทัดรัด นี่คือ format ที่ใช้ในไฟล์ config หลายตัวและ output log ที่มีโครงสร้างที่ทั้งคนและเครื่องต้องอ่านข้อมูลเดียวกัน JSON Formatter และ JSON to YAML converter จัดการการยุบนี้โดยอัตโนมัติ
7. Streaming JSON ขนาดใหญ่
การโหลดไฟล์ JSON ขนาดใหญ่เข้า memory ทั้งหมดก่อน parse คือข้อผิดพลาดด้านประสิทธิภาพที่พบบ่อยที่สุดในไปป์ไลน์การประมวลผล JSON JSON response 100 MB จัดสรรอย่างน้อย 100 MB ของ heap สำหรับสตริงดิบ จากนั้นการจัดสรรครั้งที่สองสำหรับ object ที่ parse แล้ว สำหรับไฟล์ใหญ่กว่าไม่กี่เมกะไบต์ streaming parser ประมวลผลข้อมูลแบบเพิ่มทีละน้อย
บน Node.js กับ library streaming:
// node --experimental-vm-modules (หรือใช้ streaming JSON lib)
// Streaming แบบเนทีฟกับข้อเสนอ JSON Source Map (Stage 3, 2026):
// สำหรับตอนนี้ใช้ library เช่น @streamparser/json หรือ jsonstream2
import { createReadStream } from 'fs';
import parser from '@streamparser/json';
const jsonParser = new parser.JSONParser();
jsonParser.onValue = ({ value, key, parent }) => {
if (key === 'id') {
console.log('พบ id:', value);
}
};
createReadStream('large.json').pipe(jsonParser); ในเบราว์เซอร์ใช้ Fetch Streams API:
// Streaming ฝั่ง browser กับ Fetch + JSON stream decoder:
const response = await fetch('/api/large-data');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// ประมวลผล JSON object ที่สมบูรณ์จาก buffer...
}
Streaming มีประโยชน์สูงสุดในไปป์ไลน์การประมวลผล batch, endpoint การ export ขนาดใหญ่ และสคริปต์การวิเคราะห์ log สำหรับ API response ทั่วไปต่ำกว่า 1 MB, JSON.parse มาตรฐานเร็วพอและง่ายกว่ามาก เกณฑ์ที่ streaming เริ่มคุ้มในทางปฏิบัติคือประมาณ 5-10 MB ขึ้นอยู่กับความซับซ้อนของโครงสร้างที่ parse และขนาด heap ของสภาพแวดล้อมเป้าหมาย
สำหรับการสำรวจโครงสร้างของ JSON response ขนาดใหญ่ก่อนสร้าง streaming parser, paste ตัวอย่างเข้า JSON Formatter เพื่อดูว่า path ใดมีข้อมูลที่คุณต้องการ จากนั้นแปลงเป็น CSV หรือ YAML สำหรับการวิเคราะห์เพิ่มเติม
นำมารวมกัน
เจ็ดเทคนิคนี้ครอบคลุมมุมที่มีผลกระทบสูงสุดของ JSON API:
- Deep clone ผ่าน round-trip — เร็วสำหรับข้อมูลธรรมดา รู้ข้อจำกัด
- Replacer — กรองคีย์และ mask ค่าละเอียดอ่อนที่ขอบเขต serialization
- การเรียงคีย์ — output แบบ deterministic สำหรับ cache key, signature และการ assert ในการทดสอบ
- การจัดการ BigInt — stringify เป็นสตริง revive ตอน parse
- การตรวจจับ circular reference — replacer พื้นฐาน WeakSet สำหรับ serialization ที่ปลอดภัย
- Pretty-print พร้อม array กระทัดรัด — output ที่คนอ่านได้โดยไม่มีช่องว่างเกิน
- Streaming JSON ขนาดใหญ่ — parsing แบบเพิ่มทีละน้อยสำหรับไฟล์ขนาดใหญ่และ API response
สำหรับเอกสารครบถ้วนของ replacer และ space argument ของ JSON.stringify ดู เอกสาร MDN JSON.stringify สำหรับทางเลือก deep clone สมัยใหม่ ดู เอกสาร structuredClone() ซึ่งครอบคลุมทุก type ที่รองรับและ edge case