JSON Toolkit

Binarny JSON: wybór między MessagePack, CBOR i BSON

Podsumowanie: MessagePack, CBOR i BSON kompresują JSON do bajtów. Porównaj koszt bajtowy, obsługę int64 i danych binarnych oraz zobacz wyraźną linię, kiedy przejście ma sens.

Średniozaawansowany~10 min czytaniaOpublikowano 2026-10-31Zaktualizowano 2026-10-31
Okładka porównania binarnych formatów JSON: MessagePack, CBOR i BSON

Najważniejsze: trzymaj tekstowy JSON do wymiany zewnętrznej i do danych, które ludzie muszą obejrzeć, a na łączach wewnętrznych przechodź na MessagePack lub CBOR tylko wtedy, gdy ładunki binarne, liczby całkowite powyżej 2^53 lub dokładne ułamki dziesiętne czynią skompresowany rozmiar albo siłę wyrazu typów realnym problemem; BSON należy do ekosystemu MongoDB.

Tekstowy JSON oszczędza białe znaki; formaty binarne oszczędzają interpunkcję i tekstową postać liczb. Nazwy pól nadal występują raz per rekord, więc przed przejściem oblicz realną oszczędność.

1. Co naprawdę oszczędza binarny JSON

Najpierw ustal oczekiwania. Żaden z tych formatów nie jest „inną składnią JSON”. Kodują ten sam model danych w bajtach: obiekty, tablice, łańcuchy, liczby, wartości logiczne i null. To, co usuwają, jest konkretne:

  • Znaki strukturalne, takie jak cudzysłowy, dwukropki, przecinki i nawiasy;
  • Dziesiętną postać tekstową liczb, więc 32 staje się pojedynczym bajtem;
  • Ponowne skanowanie granic łańcuchów, zastąpione prefiksem o stałej długości;
  • Kodowanie Base64 dla ładunków binarnych, które można przechowywać bezpośrednio jako bajty.

To, czego nie potrafią usunąć, jest równie konkretne: nazwy pól nadal występują jako łańcuchy w każdym rekordzie. Mapa MessagePack, mapa CBOR i dokument BSON powtarzają klucz przy każdym wpisie. Więc gdy twoje JSON to dziesięć tysięcy rekordów o tym samym kształcie, różnica skompresowanego rozmiaru między tekstem a binariem jest znacznie mniejsza, niż podpowiada intuicja, bo gzip jest najlepszy w eliminowaniu powtarzających się nazw pól.

2. Skąd pochodzą trzy formaty

Format Specyfikacja i rok Pochodzenie Typowe użycie
MessagePack spec msgpack.org, od 2008 Niezależny projekt skupiony na minimalizmie Wartości cache, IPC, potoki logów
CBOR RFC 8949 (STD 94), 2020 Zainspirowany MessagePack, ustandaryzowany przez IETF COSE, WebAuthn, CWT, urządzenia o ograniczonych zasobach
BSON bsonspec.org, ekosystem MongoDB Zaprojektowany dla dokumentów bazodanowych Przechowywanie i protokół sieciowy MongoDB

CBOR jest najlepiej ustandaryzowany: typ nośny application/cbor zarejestrowany w IANA, deterministyczne reguły kodowania i CDDL do opisu. MessagePack jest najlżejszy, ze specyfikacją mieszczącą się na jednej stronie. BSON jest najcięższy: wpisuje ObjectId, Decimal128, daty i podtypy binarne w swój system typów, dlatego jest naprawdę popularny tylko w ekosystemie MongoDB.

3. Te same dane, cztery kodowania

Weźmy {"name":"joe"} jako przykład. Koszty bajtowe są łatwe do porównania:

Kodowanie Bajty Sekwencja bajtów
Tekst 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-bajtowa długość całkowita + bajt typu + name\0 + 4-bajtowa długość wartości + joe\0 + terminator

Rozłóżmy dziewięć bajtów MessagePack na części:

  • 81: wysoki półbajt 1000 oznacza mapę, niski półbajt 0001 oznacza jedną parę klucz-wartość;
  • a3: trzy wysokie bity 101 oznaczają łańcuch o stałej długości 3;
  • 6e 61 6d 65: ASCII dla name;
  • a3: kolejny łańcuch o długości 3;
  • 6a 6f 65: joe.

a1 w CBOR to typ główny 5 (mapa) o długości 1, a 64 to typ główny 3 (łańcuch tekstowy) o długości 4, więc semantyka jest niemal izomorficzna z MessagePack. BSON jest większy: każdy dokument niesie 4-bajtową długość całkowitą, każde pole powtarza bajt typu i nazwę, a każdy łańcuch powtarza długość plus końcowy \0. Przy małych dokumentach BSON łatwo bywa większy niż tekst JSON.

Możesz sprawdzić prefiksy bezpośrednio, renderując te bajty jako hex:

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 Rozmiar i szybkość parsowania: zignoruj liczby marketingowe

Twierdzenia, że binarny format jest „10x szybszy”, należy rozbić na dwa pytania.

Rozmiar. Nieskompresowany binarny jest mniejszy, bo interpunkcja i białe znaki znikają. Ale realne potoki prawie zawsze kompresują: po gzip lub brotli powtarzające się nazwy pól zostają skompresowane, a przewaga binarna dramatycznie maleje. Podążaj metodą z Minifikacja JSON a gzip: najpierw zmierz skompresowane bajty, potem zdecyduj, czy zmiana formatu jest warta zachodu.

Szybkość parsowania. Binarny pomija leksowanie (skanowanie cudzysłowów, przecinków i znaków liczb), więc powinien być szybszy w teorii. W JavaScript dominującym kosztem jest jednak alokacja obiektów i pamięci, której żaden dekoder nie uniknie. Kształt danych, liczba pól i jakość implementacji mają znaczenie, więc jedyny wiarygodny wniosek pochodzi z benchmarkowania własnych ładunków, a nie z cytowania mnożnika dostawcy. Jeśli pojedynczy ładunek jest zbyt duży, by wczytać go naraz do pamięci, pokrój go podejściem opisanym w parsowaniu i strumieniowaniu dużego JSON, zanim zmierzysz zysk kodowania.

4. Pola int64 i binarne: prawdziwy powód zmiany

Rozmiar często nie jest powodem zmiany formatu. Siła wyrazu typów jest.

4.1 Liczby całkowite powyżej 2^53

Liczby JSON lądują w double w JavaScript, więc 9007199254740993 staje się 9007199254740992. Trzy formaty binarne radzą sobie z tym różnie:

Format Liczby całkowite 64-bit 64-bit bez znaku Dowolna precyzja
MessagePack ✓ int64 (0xd3), dekodowalne do BigInt ✓ uint64 (0xcf) ✗ (wymaga typu rozszerzenia)
CBOR ✓ 64-bitowe formy typu głównego 0/1 ✓ Ten sam obszar kodowania ✓ tagi bignum 2/3
BSON ✓ int64 (0x12) ✗ Tylko ze znakiem ✗ (Decimal128 jest stałoprzecinkowy, a nie dowolną liczbą całkowitą)

CBOR jest jedynym ze standardowym przedstawieniem liczb całkowitych o dowolnej precyzji: wartości przekraczające 64 bity są opakowane w tag 2 (nieujemne) lub tag 3 (ujemne) wokół łańcucha bajtowego. MessagePack i BSON cofają się do łańcuchów lub własnych konwencji rozszerzeń.

4.2 Ładunki binarne

JSON ma dokładnie jeden sposób wyrażania danych binarnych: Base64, które rozdyma ładunek o około jedną trzecią i wymaga dodatkowej kopii dekodowania. MessagePack ma typy bin (0xc4–0xc6), CBOR ma łańcuchy bajtowe (typ główny 2), a BSON ma podtypy binarne. Gdy transportujesz obrazy, hashes, wektory lub skompresowane bloki, ta różnica znaczy znacznie więcej niż oszczędzenie kilku bajtów interpunkcji.

5. Porównanie obok siebie

Wymiar MessagePack CBOR BSON
Specyfikacja Specyfikacja społeczności RFC 8949 IETF Ekosystem MongoDB
Typ nośny application/msgpack (nie standard IANA) application/cbor (zarejestrowany w IANA) Brak ogólnego typu nośnego
System typów Sześć typów JSON plus bin i ext Sześć typów JSON plus bin, tagi i wartości proste Szerszy: ObjectId, Decimal128, daty, regex, znaczniki czasu
Mechanizm rozszerzeń Typy rozszerzeń (np. timestamp -1) Tagi (daty, bignumy, osadzony CBOR) Stałe kody typów, rozszerzenia według konwencji ekosystemu
Deterministyczne kodowanie Brak standardowego wymogu Jawne reguły, użyteczne do podpisów Brak
Samoopisujący się Tak, ale bez schematu pól Tak, opisywalny przez CDDL Tak, z nazwami pól powtarzanymi per dokument
Narzut małych dokumentów Niski Niski Wysoki (prefiks długości, nazwy pól, terminator)
Ekosystem Cache, logi, część RPC COSE, WebAuthn, IoT Tylko MongoDB

6. Wsparcie przeglądarek i Node

Node 24 nie dostarcza żadnego z tych kodeków, więc instalujesz bibliotekę. Częste wybory:

Środowisko MessagePack CBOR BSON
Node.js @msgpack/msgpack, msgpackr cbor-x, borc bson
Przeglądarka @msgpack/msgpack kompilacja przeglądarkowa cbor-x, cbor-web bson

Obie rodziny działają na Uint8Array i nie wymagają Buffer. Uważaj na różnice wersji:

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

/**
 * Encodes an object to MessagePack bytes and decodes it immediately for verification.
 * @param payload Plain object to encode
 * @returns The decoded value and the encoded byte length
 */
function roundTrip(payload: Record<string, unknown>): { decoded: unknown; byteLength: number } {
  const bytes = encode(payload);
  return { decoded: decode(bytes), byteLength: bytes.byteLength };
}

Większość bibliotek domyślnie dekoduje duże liczby całkowite do number i traci precyzję poza bezpiecznym zakresem. Jeśli dokładne wartości mają znaczenie, jawnie włącz opcję BigInt (useBigInt64 w @msgpack/msgpack, równoważny przełącznik w cbor-x), inaczej po prostu przeniosłeś stary problem JSON-a do nowego formatu.

7. Debugowalność i zgodność protokołu

7.1 Debugowalność: największy ukryty koszt

Największą zaletą tekstowego JSON jest nie rozmiar, ale to, że możesz go otworzyć w każdej chwili. Po przejściu na binarny:

  • grep, awk i jq przestają działać, a potoki logów wymagają dodatkowego etapu dekodowania;
  • Debugowanie produkcji wymaga właściwego dekodera we właściwej wersji, inaczej zobaczysz złe nazwy pól;
  • Przechwycony ruch pojawia się w narzędziach deweloperskich przeglądarki jako nieczytelny blok bajtów;
  • Diffy przeglądu kodu pokazują ciąg szesnastkowy.

CBOR ma notację diagnostyczną, która renderuje bajty z powrotem do czytelnego tekstu zbliżonego do JSON, na przykład mapę jako {1: 2}; MessagePack też ma kilka dekoderów wiersza poleceń. Ale wszystkie wymagają dodatkowej instalacji i leżą poza standardową ścieżką operacji.

Wniosek: gdy tylko ludzie muszą często przeglądać dane, ukryty koszt formatu binarnego przewyższa oszczędzoną przepustowość.

7.2 Zgodność protokołu i rozszerzenia

  • CBOR ma najbardziej kompletny mechanizm tagów: tag 0 dla łańcucha data-czas, tag 1 dla znacznika czasu Unix, tagi 2 i 3 dla bignumów, tag 24 dla osadzonego CBOR. W połączeniu z deterministycznym kodowaniem jest używany przez protokoły wymagające podpisów i spójności między implementacjami, takie jak COSE, WebAuthn i CWT.
  • MessagePack zostawia typy rozszerzeń implementatorom. W praktyce jedyny powszechny to timestamp (typ rozszerzenia -1). Między zespołami każdy typ rozszerzenia musi trafić do dokumentu protokołu, inaczej druga strona nie potrafi go zdekodować.
  • BSON ma stałe kody typów, a ObjectId, Decimal128, MinKey i MaxKey to semantyka specyficzna dla MongoDB. Traktowanie BSON jako ogólnego formatu wymiany przynosi stos nieużywanych typów i nieużywanych prefiksów długości.

Jeśli protokół wymaga „te same dane muszą dawać te same bajty”, tylko CBOR daje standardową odpowiedź; MessagePack i BSON wymagają dodatkowych ustaleń co do kolejności pól i szczegółów kodowania.

8. Kiedy warto się przełączyć

Sygnały uzasadniające przejście:

  • Ładunki są duże, oba końce można zmienić, a skompresowane rozmiary nadal istotnie się różnią;
  • Transportujesz treść binarną, a rozdęcie Base64 i koszt dekodowania są wymierne;
  • Potrzebujesz 64-bitowych liczb całkowitych lub dokładnych ułamków dziesiętnych, których double w JSON-ie nie potrafi przedstawić;
  • Łącze jest serwer-do-serwera i narzędzia diagnostyczne można ustandaryzować.

Sygnały przeciwne:

  • gzip lub brotli już jest, a zysk to jednocyfrowy procent;
  • API jest publiczne, a podmioty zewnętrzne mają tylko ogólne klienty HTTP;
  • Dane to logi lub konfiguracja czytana ręcznie;
  • Zespół nie ma budżetu na utrzymanie dekoderów i zgodności wersji.

Działa też ścieżka pośrednia: trzymaj JSON zewnętrznie i używaj MessagePack do wewnętrznych cache. Wartości cache czytają tylko twoje własne usługi, więc zarówno rozmiar, jak i koszt parsowania się poprawiają, a wyczyszczenie cache rozwiązuje każdy problem bez dotykania kontraktu.

9. Lista kontrolna

  • [ ] Porównuj skompresowane liczby bajtów z prawdziwych danych, a nie liczby nieskompresowane;
  • [ ] Ustal politykę dużych liczb całkowitych: opcja BigInt, transport łańcuchowy albo bignumy CBOR;
  • [ ] Wysyłaj pola binarne jako bin lub łańcuchy bajtowe, zamiast opakowywać je w Base64;
  • [ ] Wpisz typy rozszerzeń i tagi do dokumentu protokołu i przypnij wersje dekoderów;
  • [ ] Zachowaj ścieżkę debugowania tekstowym JSON-em, którą można włączyć podczas incydentów produkcyjnych;
  • [ ] Przeprowadź benchmark i zapisz powtarzalne liczby, zamiast twierdzeń słownych.

Jaka jest różnica między MessagePack a CBOR?

Ich kodowania są bardzo podobne: mapy, tablice i łańcuchy używają zwartego prefiksu typ-plus-długość. Główna różnica to standaryzacja. CBOR to RFC IETF z typem nośnym IANA, mechanizmem tagów i deterministycznymi regułami kodowania; MessagePack to specyfikacja społeczności, której typy rozszerzeń opierają się na prywatnych ustaleniach. Wybierz CBOR, gdy potrzebujesz podpisów, spójności między implementacjami lub protokołów dla urządzeń o ograniczonych zasobach.

Czy BSON może zastąpić JSON jako format ogólny?

Nie jest to zalecane. BSON zaprojektowano dla dokumentów bazodanowych: każdy dokument niesie 4-bajtową długość całkowitą, każde pole powtarza typ i nazwę, a małe dokumenty kończą się większe niż tekst JSON. Zawiera też typy specyficzne dla MongoDB, takie jak ObjectId i MinKey. Poza MongoDB większość jego systemu typów jest nieużywana, a kara rozmiarowa zostaje.

O ile mniejszy czyni mój ładunek format binarny?

Zależy od kształtu danych. Gdy nazwy pól powtarzają się intensywnie, skompresowany tekstowy JSON i formaty binarne wypadają podobnie; gdy nazwy są krótkie, a dane pełne znaków strukturalnych i liczb, przewaga binarna jest wyraźniejsza. Zmierz prawdziwe próbki zarówno nieskompresowane, jak i po gzip, a potem zdecyduj, zamiast szacować ogólnym mnożnikiem.

Czy nadal mogę debugować dane binarne przez jq?

Nie. jq przetwarza tylko tekstowy JSON i natychmiast zawiedzie na bajtach MessagePack lub CBOR. Musisz najpierw zdekodować pasującą biblioteką do JSON i dopiero podać go jq; CBOR może też przejść przez narzędzie notacji diagnostycznej, aby stać się czytelnym tekstem. Utrzymanie ścieżki debugowania tekstem jest tańsze niż instalowanie narzędzi na żądanie.