JSON Toolkit

เร่งความเร็วการ parse JSON ขนาดใหญ่

สรุปสำคัญ: การ optimize ผิดลำดับทำให้การเปลี่ยนไลบรารีไร้ประโยชน์ วัด network, parse และ render แยกกันก่อน แล้วจึงลดจำนวน parse ย่อ payload และ stream

ระดับกลางอ่านประมาณ 5 นาทีเผยแพร่ 2026-10-30อัปเดต 2026-10-30
ลำดับการ optimize การ parse JSON ขนาดใหญ่

สรุปสั้น ๆ: วัด network, parse และ render แยกกันก่อน — "การ parse ช้า" ส่วนใหญ่กลายเป็นการ render ช้า — แล้วไต่บันไดตามลำดับ: parse น้อยลง, payload เล็กลง, stream, worker และรูปแบบ binary เป็นขั้นสุดท้าย

อย่าเริ่มจากการเปลี่ยนไลบรารี เพิ่ม timer ตัวเดียวแล้วคุณมักจะพบว่า render คือปัญหา

ขั้นแรก: วัดแต่ละช่วง

const t0 = performance.now();
const response = await fetch(url);
const text = await response.text();
const t1 = performance.now();
const data = JSON.parse(text);
const t2 = performance.now();
render(data);
const t3 = performance.now();
ช่วงที่ครองเวลา ปัญหาจริง วิธีแก้
Network Payload ใหญ่เกินไป การเลือกฟิลด์, pagination, compression
JSON.parse ข้อมูลใหญ่จริง parse น้อยลง, streaming, worker
Render หลายหมื่น node พร้อมกัน virtual list, แบ่งหน้า, ย่อ/ขยาย

เบราว์เซอร์สมัยใหม่ parse 1MB ได้ราว 5–15ms และ 10MB ใน 80–150ms ถ้า 10MB ใช้เวลาสองวินาที JSON.parse ไม่ใช่คอขวดของคุณ

ขั้นที่สอง: parse น้อยลง

ชัยชนะที่ถูกที่สุดและถูกละเลยมากที่สุด

  • เลิก parse payload เดิมซ้ำ ๆ คอมโพเนนต์สิบตัวที่ต่างเรียก JSON.parse(localStorage.getItem(...)) คือการ parse สิบครั้ง ให้ cache ครั้งเดียวระดับ module
  • เลิกใช้ JSON.parse(JSON.stringify(x)) สำหรับ deep copy มันกินมากกว่าปริมาณข้อมูลสองเท่า; structuredClone ไม่เป็นเช่นนั้น
  • อย่า parse บน main thread แล้วส่ง object ไป worker postMessage จะ clone อีกครั้ง ให้ส่งข้อความดิบไปและให้ worker parse
  • ขอฟิลด์ให้น้อยลง ถ้า UI ต้องการสามพร็อพเพอร์ตี้ API ก็ไม่ควรส่งสามร้อย

ขั้นที่สาม: ย่อ payload

Pagination ด้วย page/size, ใช้ cursor แทน offset ที่ลึก, ฟิลด์สรุปใน endpoint แบบ list สิ่งนี้มักชนะการ optimize ฝั่ง frontend ทั้งหมดรวมกัน เพราะลด network, parse และหน่วยความจำพร้อมกัน

ขั้นที่สี่: stream

เมื่อข้อมูลต้องประมวลผลทั้งก้อนและใหญ่จริง:

import { parser } from 'stream-json';
import { streamArray } from 'stream-json/streamers/StreamArray';

const pipeline = fs.createReadStream('big.json').pipe(parser()).pipe(streamArray());
for await (const { value } of pipeline) {
  await handleOne(value);
}

โค้ดมากขึ้น และไม่มี random access — เหมาะเฉพาะการประมวลผลทีละ record

ขั้นที่ห้า: ย้ายออกจาก main thread

การ parse เร็วแต่บล็อก ให้ใส่ใน worker และส่ง ข้อความ ไม่ใช่ object ที่ parse แล้ว มิฉะนั้น structured cloning จะทำให้ต้นทุนเพิ่มเป็นสองเท่า

ขั้นที่หก: เปลี่ยนรูปแบบ เป็นขั้นสุดท้าย

สำหรับทราฟฟิกภายในที่ใหญ่ระดับหลายสิบเมกะไบต์เสมอ NDJSON stream ได้เป็นธรรมชาติ และ Protobuf หรือ MessagePack เล็กและเร็วกว่า ทั้งสองเป็นการเปลี่ยนโปรโตคอลที่ต้องประสานกัน

สิ่งที่ไม่ควรทำ

  • eval JSON: ไม่ปลอดภัยและไม่เร็วกว่า
  • Stringify JSON เพื่อฝังลงใน JSON: ขนาดเพิ่มเป็นสองเท่า
  • parse สตริงเล็ก ๆ หลายแสนตัวในลูป: ให้ทำเป็นชุด

คำถามที่พบบ่อย

จะรู้ได้อย่างไรว่า parse หรือ render คือคอขวด?

จับเวลาการ request, การอ่านข้อความ, JSON.parse และการ render แยกกันด้วย performance.now() แล้วถือช่วงที่ใช้เวลามากที่สุดเป็นคอขวดจริง เบราว์เซอร์สมัยใหม่ parse 1MB ได้ราว 5–15ms และ 10MB ใน 80–150ms ดังนั้น payload 10MB ที่ใช้สองวินาทีจึงไม่ค่อยเป็นปัญหา JSON.parse

ทำไมการเปลี่ยนไลบรารีมักไม่ช่วย?

JSON.parse เป็น native และเร็วอยู่แล้ว; "การ parse ช้า" ส่วนใหญ่มาจากการ parse ซ้ำหรือการ render หลายหมื่น node พร้อมกัน การเปลี่ยนไลบรารีได้ผล 10–30% ขณะที่การลบการ parse เต็มรูปแบบหนึ่งครั้งมักประหยัดได้ครึ่งหนึ่ง

ควรย้ายการ parse ไป Web Worker ไหม?

ย้ายไป worker เมื่อการ parse บล็อก main thread และหน้าค้างหรือแอนิเมชันตกเฟรม ให้ส่งข้อความดิบแทน object ที่ parse แล้ว มิฉะนั้น structured cloning ของ postMessage จะทำให้ต้นทุนเพิ่มเป็นสองเท่า

อ่านเพิ่มเติม