JSON 工具箱

二進位 JSON:MessagePack、CBOR 與 BSON 的取捨

結論:MessagePack、CBOR、BSON 都把 JSON 的資料模型壓成位元組,但體積、型別系統與生態完全不同。本文用同一份資料比較位元組開銷,說明 int64 與二進位欄位的處理,並給出「什麼時候值得換」的判斷線。

進階約 10 分鐘發佈於 2026-10-31更新於 2026-10-31
MessagePack、CBOR 與 BSON 二進位 JSON 對比封面

結論:對外交換和需要人檢視的資料保持文字 JSON,只有當二進位內容、超過 2^53 的整數或精確小數讓壓縮後的體積與型別表達真正吃虧時,才在內部鏈路換 MessagePack 或 CBOR;BSON 只屬於 MongoDB 生態。

文字 JSON 省掉的是空白,二進位格式省掉的是標點與數字的字元表示;欄位名照樣一條一份,所以換格式前先算清楚到底省了多少。

一、二進位 JSON 到底省了什麼

先把期待放對位置。這三種格式都不是「另一種 JSON 語法」,而是用位元組表達同一套資料模型:物件、陣列、字串、數字、布林、空值。它們省掉的東西很具體:

  • 引號、冒號、逗號、括號等結構性字元;
  • 數字的十進位文字表示,比如 32 變成一個位元組;
  • 字串前的長度宣告由定長前綴表示,省去重新掃描;
  • 二進位內容可以直接是位元組串,不必先做 Base64。

省不掉的東西同樣具體:欄位名仍然以字串形式逐條出現。MessagePack 的 map、CBOR 的 map、BSON 的 document,都沒有「欄位名只出現一次」的機制。因此當你的 JSON 是一萬條同構記錄時,壓縮後的文字 JSON 與二進位格式的體積差距會明顯小於直覺,因為 gzip 最擅長的就是消除重複欄位名。

二、三種格式的出身

格式 規範與年份 設計源頭 典型用途
MessagePack msgpack.org 規範,2008 年起 獨立設計,強調極簡與緊湊 快取值、程序間通訊、日誌管道
CBOR RFC 8949(STD 94),2020 年 受 MessagePack 影響,IETF 標準化 COSE、WebAuthn、CWT、受限裝置
BSON bsonspec.org,MongoDB 生態 為資料庫文件設計 MongoDB 儲存與線路協定

CBOR 的標準化程度最高,有 IANA 註冊的 application/cbor 媒體類型、確定性編碼規則與 CDDL 描述語言;MessagePack 最輕,規範一頁能讀完;BSON 最重,它把 ObjectId、Decimal128、日期、二進位子型別都做進了型別系統,也因此只在 MongoDB 生態裡真正流行。

三、同一份資料,四種位元組

以 {"name":"joe"} 為例,四種表示的位元組開銷差別很直觀:

表示 位元組數 位元組序列
JSON 文字 14 7b 22 6e 61 6d 65 22 3a 22 6a 6f 65 22 7d
MessagePack 9 81 a3 6e 61 6d 65 a3 6a 6f 65
CBOR 10 a1 64 6e 61 6d 65 63 6a 6f 65
BSON 19 總長 4 位元組 + 型別 1 位元組 + name\0 + 值長 4 位元組 + joe\0 + 終止符

逐位元組拆開 MessagePack 那 9 個位元組:

  • 81:高 4 位 1000 表示 map,低 4 位 0001 表示 1 個鍵值對;
  • a3:高 3 位 101 表示定長字串,長度 3;
  • 6e 61 6d 65:name 的 ASCII;
  • a3:又是一個長度 3 的字串;
  • 6a 6f 65:joe。

CBOR 的 a1 是 major type 5(map)加長度 1,64 是 major type 3(文字字串)加長度 4,語意與 MessagePack 幾乎同構。BSON 反而更大:每個文件都要帶 4 位元組總長,每個欄位都要重複型別位元組與欄位名,字串還要再帶一個長度和結尾的 \0。小文件上,BSON 比 JSON 文字還長並不奇怪。

把這段位元組還原成十六進位,可以直接核對前綴:

const messagePackBytes = [0x81, 0xa3, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x6a, 0x6f, 0x65];
const hexText = messagePackBytes.map((byte) => byte.toString(16)).join(' ');
console.log(hexText); // 81 a3 6e 61 6d 65 a3 6a 6f 65

3.1 體積與解析速度:別信宣傳數字

關於「二進位快 10 倍」的說法,先拆成兩件事看:

體積。 未壓縮時,二進位確實更小,因為省掉了標點與空白。但真實鏈路幾乎都會壓縮:文字 JSON 經過 gzip 或 brotli 後,重複欄位名被高度壓縮,二進位格式的領先幅度會縮到很小。可以對照 JSON 壓縮與 gzip 的取捨 裡的判斷方法:先量壓縮後的實際位元組,再決定要不要換格式。

解析速度。 二進位省掉了詞法分析(掃描引號、逗號、數字字元),理論上更快。但 JavaScript 裡真正的成本大頭是建立物件與分配記憶體,解碼器再快也繞不過這一步。資料形狀、欄位數量、實作品質都會影響結果,唯一可靠的結論來自用自己的資料做基準測試,而不是照抄宣傳頁上的倍數。如果單條負載已經大到無法整體讀入記憶體,先用 大 JSON 的解析與串流處理 裡的分片方法切塊,再比較編碼收益。

四、int64 與二進位欄位:換格式的真正理由

很多時候,體積不是換格式的理由,型別表達力才是。

4.1 超過 2^53 的整數

JSON 數字在 JavaScript 裡落到雙精度浮點,9007199254740993 會被解析成 9007199254740992。三種二進位格式的處理方式:

格式 64 位整數 無號 64 位 任意精度
MessagePack ✓ int64(0xd3),解碼可給 BigInt ✓ uint64(0xcf) ✗(需擴充型別)
CBOR ✓ major type 0/1 的 64 位形式 ✓ 同一編碼空間 ✓ bignum 標籤 2/3
BSON ✓ int64(0x12) ✗ 只有有號型別 ✗(Decimal128 定點,不是任意精度整數)

CBOR 是三者裡唯一對任意精度整數有標準表示的:超過 64 位的整數用 tag 2(非負)或 tag 3(負)包一段位元組串。MessagePack 與 BSON 遇到超大整數只能退回字串,或者在擴充型別裡自訂約定。

4.2 二進位內容

JSON 表達二進位只有一條路:Base64,體積膨脹約三分之一,解碼還要額外一次拷貝。MessagePack 有 bin 型別(0xc4–0xc6),CBOR 有 byte string(major type 2),BSON 有 binary 子型別。傳圖片、雜湊、向量、壓縮塊時,這個差異比「省幾個標點」重要得多。

五、三者對比表

維度 MessagePack CBOR BSON
規範來源 社群規範 IETF RFC 8949 MongoDB 生態
媒體類型 application/msgpack(非 IANA 標準) application/cbor(IANA 註冊) 無通用媒體類型
型別系統 JSON 六類 + bin + ext JSON 六類 + bin + tag + 簡單值 更寬:ObjectId、Decimal128、日期、正規表達式、時間戳
擴充機制 extension type(如 timestamp -1) tag(日期、bignum、內嵌 CBOR 等) 固定型別碼,擴充靠生態約定
確定性編碼 無標準要求 有明確規則,可用於簽章 無
自描述 是,但無欄位級 schema 是,可配 CDDL 描述 是,文件內重複欄位名
小文件開銷 低 低 高(長度前綴 + 欄位名 + 終止符)
生態 快取、日誌、部分 RPC COSE、WebAuthn、IoT MongoDB 專用

六、瀏覽器與 Node 支援

Node 24 沒有內建這三種編解碼器,需要裝函式庫。常用組合:

執行環境 MessagePack CBOR BSON
Node.js @msgpack/msgpack、msgpackr cbor-x、borc bson
瀏覽器 @msgpack/msgpack cbor-x 的瀏覽器建置、cbor-web bson

兩類實作都圍繞 Uint8Array 工作,不需要額外的 Buffer。需要注意版本差異:

import { encode, decode } from '@msgpack/msgpack';

/**
 * 將物件編碼為 MessagePack 位元組並立即解碼校驗。
 * @param payload 待編碼的普通物件
 * @returns 解碼結果與編碼後的位元組長度
 */
function roundTrip(payload: Record<string, unknown>): { decoded: unknown; byteLength: number } {
  const bytes = encode(payload);
  return { decoded: decode(bytes), byteLength: bytes.byteLength };
}

大整數在多數函式庫裡預設解碼成 number,超過安全整數範圍時會丟精度;需要精確值時,要顯式開啟 BigInt 選項(@msgpack/msgpack 提供 useBigInt64,cbor-x 有對應開關),否則等於把 JSON 的老問題原樣搬了過來。

七、可除錯性與協定相容

7.1 可除錯性:最大的隱性成本

文字 JSON 最大的優勢不是體積,而是任何時候都能打開看。換成二進位之後:

  • grep、awk、jq 全部失效,日誌管道要額外一步解碼;
  • 排查線上問題時,先要拿到正確的解碼器與版本,否則看到的欄位名可能是亂的;
  • 擷取封包結果在瀏覽器開發者工具裡顯示為一段不可讀的位元組;
  • 程式碼審查時,diff 裡出現的是一串十六進位。

CBOR 有診斷記法(diagnostic notation),能把位元組還原成接近 JSON 的可讀文字,例如 map 寫作 {1: 2};MessagePack 也有若干命令列解碼工具。但這些都需要額外安裝,且不在標準維運鏈路裡。

結論:只要這份資料需要人頻繁查看,二進位格式的隱性成本就會超過它省下的頻寬。

7.2 協定相容與擴充

  • CBOR 的 tag 機制最完整:tag 0 表示日期時間字串,tag 1 表示 Unix 時間戳,tag 2/3 表示大整數,tag 24 表示內嵌一段 CBOR。配合確定性編碼,它被 COSE、WebAuthn、CWT 這些需要簽章與跨實作一致性的協定採用。
  • MessagePack 的擴充型別留給實作方自行約定,常見的只有 timestamp(擴充型別 -1)。跨團隊使用時,擴充型別必須寫進協定文件,否則對方解不出來。
  • BSON 的型別碼是固定的,ObjectId、Decimal128、MinKey/MaxKey 都是 MongoDB 專屬語意。把 BSON 當成通用交換格式,會帶來一堆用不上的型別和用不上的長度前綴。

如果協定要求「同樣的資料必須編碼成同樣的位元組」,只有 CBOR 給出了標準答案;MessagePack 與 BSON 都需要額外約定欄位順序與編碼細節。

八、什麼時候值得換

值得換的訊號:

  • 單次傳輸的資料量大,且兩端都能改程式,壓縮後的體積仍有明顯差距;
  • 需要傳二進位內容,Base64 的膨脹與解碼成本已經可測;
  • 需要 64 位整數、精確小數,而 JSON 的雙精度會丟精度;
  • 伺服器到伺服器的內部鏈路,排查工具可以統一配好。

不值得換的訊號:

  • 已經用 gzip 或 brotli 壓縮,收益只剩個位數百分比;
  • 對外公開 API,第三方只能用通用 HTTP 客戶端;
  • 日誌、配置檔案等需要人眼查看的內容;
  • 團隊沒有維護解碼工具與版本相容的預算。

中間路線也成立:對外保持 JSON,內部快取用 MessagePack。快取值只被自家服務讀取,體積與解析成本都能省,出問題時把快取清掉即可,不影響契約。

九、實作清單

  • [ ] 先用真實資料比較壓縮後的位元組數,而不是未壓縮的位元組數;
  • [ ] 明確大整數策略:BigInt 選項、字串承載,還是換成 CBOR 的 bignum;
  • [ ] 二進位欄位直接走 bin / byte string,不再套一層 Base64;
  • [ ] 擴充型別與 tag 寫進協定文件,並固定解碼器版本;
  • [ ] 保留一條文字 JSON 的除錯通道,線上排障時能切過去;
  • [ ] 做一次基準測試,把「快多少」寫成可重現的數字,而不是口頭結論。

MessagePack 和 CBOR 有什麼區別?

兩者編碼結構高度相似:map、陣列、字串都用「型別位 + 長度」的緊湊前綴。主要差別在標準化程度:CBOR 是 IETF RFC,有 IANA 媒體類型、tag 機制與確定性編碼規則;MessagePack 是社群規範,擴充型別靠自訂約定。需要簽章、跨實作一致性或受限裝置協定時選 CBOR。

BSON 能當通用 JSON 替代品嗎?

不建議。BSON 為資料庫文件設計,每個文件帶 4 位元組總長、每個欄位重複型別與欄位名,小文件比 JSON 文字還大;它還包含 ObjectId、MinKey 等 MongoDB 專屬型別。離開 MongoDB 生態後,它的型別系統大部分用不上,體積劣勢卻照舊存在。

換成二進位格式後,體積能小多少?

取決於資料形狀。欄位名重複率高時,文字 JSON 壓縮後與二進位的差距很小;欄位名短、結構字元多、數字多時,二進位優勢更明顯。正確做法是拿真實樣本分別測「未壓縮」與「gzip 後」兩組數字,再做決定,不要用通用倍數估算。

二進位格式還能用 jq 除錯嗎?

不能。jq 只處理文字 JSON,遇到 MessagePack 或 CBOR 位元組會直接報解析錯誤。除錯時需要先用對應函式庫解碼成 JSON,再交給 jq;CBOR 還可以用診斷記法工具轉成可讀文字。因此保留一條文字除錯通道,比臨時裝工具更省時間。