JSON vs Binary
คุณตัดสินใจแล้วว่าจะส่งหนึ่ง object ที่มีโครงสร้างต่อหนึ่ง message ทางแยกถัดไปคือ จะ encode object นั้นบน wire อย่างไร WebSocket รับส่งได้ทั้ง text หรือ binary และทางเลือกเดียวนั้น — JSON เทียบกับ format แบบ binary — ส่งผลเงียบ ๆ ต่อความ debug ได้ง่าย ต่อ bandwidth และต่อ CPU ของคุณ
JSON: อ่านได้และอยู่ทุกที่
หัวข้อที่มีชื่อว่า “JSON: อ่านได้และอยู่ทุกที่”JSON.stringify และ JSON.parse ถูกฝังอยู่ในทุกเบราว์เซอร์และทุก server runtime JSON นั้น อธิบายตัวเองได้ (self-describing) (ชื่อ field เดินทางไปพร้อมกับข้อมูล) มนุษย์อ่านได้ใน network inspector และทุกภาษาเข้าใจได้โดยไม่ต้องมีขั้น build สำหรับแอปพลิเคชันส่วนใหญ่อย่างท่วมท้น — chat, dashboards, notifications, การแก้ไขร่วมกัน — JSON คือค่าเริ่มต้นที่ถูกต้อง คุณอ่าน message ได้ในพริบตา และการรับ client ใหม่เข้ามาก็แค่ตกลงกันเรื่องชื่อ field เท่านั้น ไม่มีอะไรมากกว่านั้น
ต้นทุนก็มีจริงไม่แพ้กัน ชื่อ field ซ้ำอยู่ใน ทุก message ตัวเลขเก็บเป็น text ฐานสิบ และไม่มี type ดั้งเดิมสำหรับ bytes ดิบ position update อย่าง {"x":12,"y":48} ใช้ bytes ส่วนใหญ่ไปกับ quotes, braces และตัวอักษร x กับ y มากกว่าค่าจริง
Binary: กระชับและเร็ว
หัวข้อที่มีชื่อว่า “Binary: กระชับและเร็ว”format การ serialize แบบ binary แลกความอ่านได้กับความหนาแน่น:
- MessagePack — “JSON แต่เป็น binary” data model เดียวกัน (objects, arrays, numbers, strings) ไม่มี schema เหมือน JSON แต่ encode อย่างกระชับ มักเป็นการอัปเกรดจาก JSON ที่ง่ายที่สุด
- Protocol Buffers (Protobuf) — schema-first คุณประกาศรูปร่าง message ในไฟล์
.protoชื่อ field ไม่เคยเดินทางบน wire (มีแค่ numeric tag เล็ก ๆ เท่านั้น) จึงกระชับที่สุดและ parse เร็วที่สุด แลกมากับขั้น code-generation และความยืดหยุ่นแบบ ad-hoc ที่น้อยลง - CBOR — format แบบ binary มาตรฐาน IETF (RFC 8949) ในตระกูล MessagePack พบได้ทั่วไปใน IoT และสภาพแวดล้อมที่มีข้อจำกัด
สิ่งเหล่านี้เปล่งประกายเมื่อ message เล็ก ถี่ และมีปริมาณสูง: สถานะเกม multiplayer ที่ 60 อัปเดตต่อวินาที, market data feed, telemetry จาก sensor หลายพันตัว การเฉือน bytes และเวลา parse ต่อ message สะสมเพิ่มขึ้นอย่างรวดเร็ว
flowchart LR
subgraph json["JSON (text frame)"]
j1["readable · universal · self-describing"]
j2["larger · field names repeated"]
end
subgraph bin["Binary (binary frame)"]
b1["compact · fast to parse"]
b2["needs tooling · not human-readable"]
end
json -- "high volume? tiny messages?" --> bin
bin -- "debuggability? simplicity?" --> json จะเลือกอย่างไร
หัวข้อที่มีชื่อว่า “จะเลือกอย่างไร”หลักคิดเชิงปฏิบัติ:
- ตั้งค่าเริ่มต้นเป็น JSON ใช้ไปก่อนจนกว่าจะวัดได้ว่ามีปัญหาจริง ความอ่านง่ายและการไม่ต้องมี tooling มีค่ามาก และแอปส่วนใหญ่ยังห่างจากขีดจำกัด bandwidth หรือ CPU ที่รูปแบบ encode จะเริ่มมีผลอยู่อีกไกล
- เปลี่ยนเป็น binary เมื่อตัวเลขเรียกร้อง — อัตรา message สูง, payload ใหญ่, mobile client บนข้อมูลที่คิดค่าตามปริมาณ หรือ budget latency ที่เข้มงวด วัดก่อน profiler ชนะการคาดเดาเสมอ
- คุณผสมกันได้ ส่ง message ควบคุม/ตั้งค่าเป็น JSON เพื่อความชัดเจน และส่งสตรีมร้อนความถี่สูงเป็น binary วินัย field
typeจากบทเรียนที่แล้วใช้ได้กับ encoding ทั้งสองแบบ
เดโม JSON และรูปร่างของ binary ในcode
หัวข้อที่มีชื่อว่า “เดโม JSON และรูปร่างของ binary ในcode”เดโมที่รันได้ด้านล่างส่ง JSON message แล้วรายงานขนาดจริงบน wire — ให้เห็นกับตาว่าโครงสร้างเองกินไปกี่ bytes โดยใช้ WebSocket API จริงบน echo socket ภายในหน้า
ในการตั้งค่า binary จริง คุณจะ encode object เดียวกัน ด้วย library ก่อนส่งออกเป็น binary frame ถ้าใช้ MessagePack รูปร่างจะหน้าตาแบบนี้ — สังเกต binary frame และ receive type ที่เป็น arraybuffer:
import { encode, decode } from '@msgpack/msgpack';
// Tell the socket to hand binary messages back as ArrayBuffer, not Blob.const ws = new WebSocket('wss://your-server');ws.binaryType = 'arraybuffer';
ws.onopen = () => { const update = { type: 'pos', x: 12, y: 48 }; const bytes: Uint8Array = encode(update); // compact binary, no repeated field-name text ws.send(bytes); // sent as a BINARY frame, not text};
ws.onmessage = (event: MessageEvent) => { // event.data is an ArrayBuffer for binary frames. const msg = decode(new Uint8Array(event.data)) as { type: string; x: number; y: number }; console.log(msg.type, msg.x, msg.y);};ตรรกะของแอปพลิเคชันเหมือนกับเวอร์ชัน JSON ทุกประการ — มีแค่การเรียก encode/decode และ frame type เท่านั้นที่เปลี่ยน นั่นคือเหตุผลที่คุณเริ่มด้วย JSON ได้และค่อย migrate hot path ไปเป็น binary ทีหลังหากการวัดเรียกร้อง
| Format | ขนาด | Debug | Setup |
|---|---|---|---|
| JSON | ใหญ่กว่า (field names ซ้ำ) | อ่านได้ใน DevTools | ไม่ต้องมี tooling |
| MessagePack | เล็กกว่า JSON ~30-40% | ต้อง decode ก่อนอ่าน | install library |
| Protobuf | เล็กที่สุด, schema-first | ต้อง .proto + generated code | build step |
| CBOR | เล็กกว่า JSON, IETF standard | ต้อง decode | library |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”เปลี่ยนเป็น Binary ก่อนวัด Performance อาการ:
- เปลี่ยน JSON เป็น MessagePack โดยไม่มีหลักฐานว่าช้าจริง
- เพิ่ม complexity โดยไม่ได้ benefit ที่ชัดเจน
- ใช้ JSON ก่อน วัด latency/bandwidth ถ้าเป็น bottleneck จริงค่อยเปลี่ยน
ผสม Binary และ JSON ใน Connection เดียวโดยไม่มี Protocol อาการ:
- บาง message เป็น JSON บาง message เป็น binary โดยไม่มี type indicator
onmessageต้อง guess ว่า frame ไหนเป็น format อะไร- กำหนด convention ชัดเจน: control message = JSON, data stream = binary
💡 ตัวอย่างจากของจริง
Binance WebSocket Streams:
- ใช้ JSON สำหรับ subscription management
- ใช้ binary (custom binary format) สำหรับ high-frequency order book update — ลด payload ได้ 60%
Figma Multiplayer:
- ใช้ binary (custom format) สำหรับ drawing operation — compact สำหรับ position/color/stroke data
- JSON สำหรับ metadata และ configuration