Binary JSON: Choosing Between MessagePack, CBOR, and BSON
Bottom line: MessagePack, CBOR and BSON all compress JSON into bytes. Compare byte cost, int64 and binary handling, and get a clear line for when switching is worth it.

Bottom line: keep text JSON for external exchange and for data people need to inspect, and switch to MessagePack or CBOR on internal links only when binary payloads, integers beyond 2^53, or exact decimals make compressed size or type expressiveness a real problem; BSON belongs to the MongoDB ecosystem.
Text JSON saves whitespace; binary formats save the punctuation and the textual form of numbers. Field names still appear once per record, so work out the real saving before switching.
1. What binary JSON actually saves
Set expectations first. None of these formats is "another JSON syntax". They encode the same data model in bytes: objects, arrays, strings, numbers, booleans, and null. What they remove is concrete:
- Structural characters such as quotes, colons, commas, and braces;
- The decimal text form of numbers, so
32becomes a single byte; - Re-scanning for string boundaries, replaced by a fixed-length prefix;
- Base64 encoding for binary payloads, which can be stored as bytes directly.
What they cannot remove is equally concrete: field names still appear as strings in every record. A MessagePack map, a CBOR map, and a BSON document all repeat the key for each entry. So when your JSON is ten thousand records with the same shape, the compressed size gap between text and binary is much smaller than intuition suggests, because gzip is best at eliminating repeated field names.
2. Where the three formats come from
| Format | Specification and year | Origin | Typical use |
|---|---|---|---|
| MessagePack | msgpack.org spec, since 2008 | Independent design focused on minimalism | Cache values, IPC, log pipelines |
| CBOR | RFC 8949 (STD 94), 2020 | Influenced by MessagePack, standardised by the IETF | COSE, WebAuthn, CWT, constrained devices |
| BSON | bsonspec.org, MongoDB ecosystem | Designed for database documents | MongoDB storage and wire protocol |
CBOR is the most standardised: an IANA-registered application/cbor media type, deterministic encoding rules, and CDDL for description. MessagePack is the lightest, with a specification that fits on a page. BSON is the heaviest: it bakes ObjectId, Decimal128, dates, and binary subtypes into its type system, which is why it is only truly popular inside the MongoDB ecosystem.
3. The same data, four encodings
Take {"name":"joe"} as an example. The byte costs are easy to compare:
| Encoding | Bytes | Byte sequence |
|---|---|---|
| JSON text | 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-byte total length + type byte + name\0 + 4-byte value length + joe\0 + terminator |
Split the nine MessagePack bytes apart:
81: the high nibble1000means map, the low nibble0001means one key-value pair;a3: the high three bits101mean a fixed-length string of length 3;6e 61 6d 65: ASCII forname;a3: another string of length 3;6a 6f 65:joe.
CBOR's a1 is major type 5 (map) with length 1, and 64 is major type 3 (text string) with length 4, so the semantics are nearly isomorphic to MessagePack. BSON is bigger: every document carries a 4-byte total length, every field repeats a type byte and its name, and every string repeats a length plus a trailing \0. On small documents, BSON can easily be larger than JSON text.
You can check the prefixes directly by rendering those bytes as 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 Size and parse speed: ignore the marketing numbers
Claims that binary is "10x faster" should be split into two questions.
Size. Uncompressed, binary is smaller because punctuation and whitespace are gone. But real pipelines almost always compress: after gzip or brotli, repeated field names are compressed away, and the binary advantage shrinks dramatically. Follow the method in JSON minification versus gzip: measure compressed bytes first, then decide whether a format change is worth it.
Parse speed. Binary skips lexing (scanning quotes, commas, and number characters), so it should be faster in theory. In JavaScript, however, the dominant cost is allocating objects and memory, which no decoder can avoid. Data shape, field count, and implementation quality all matter, so the only reliable conclusion comes from benchmarking your own payloads rather than quoting a vendor's multiplier. If a single payload is too large to read into memory at once, slice it with the approach described in parsing and streaming large JSON before measuring the encoding gain.
4. int64 and binary fields: the real reason to switch
Size is often not the reason to change formats. Type expressiveness is.
4.1 Integers beyond 2^53
JSON numbers land in a double in JavaScript, so 9007199254740993 becomes 9007199254740992. The three binary formats handle this differently:
| Format | 64-bit integers | Unsigned 64-bit | Arbitrary precision |
|---|---|---|---|
| MessagePack | ✓ int64 (0xd3), decodable to BigInt |
✓ uint64 (0xcf) |
✗ (needs an extension type) |
| CBOR | ✓ 64-bit forms of major type 0/1 | ✓ Same encoding space | ✓ bignum tags 2/3 |
| BSON | ✓ int64 (0x12) |
✗ Signed only | ✗ (Decimal128 is fixed-point, not an arbitrary integer) |
CBOR is the only one with a standard representation for arbitrary-precision integers: values beyond 64 bits are wrapped in tag 2 (non-negative) or tag 3 (negative) around a byte string. MessagePack and BSON fall back to strings or bespoke extension conventions.
4.2 Binary payloads
JSON has exactly one way to express binary: Base64, which inflates the payload by roughly a third and requires an extra decode copy. MessagePack has bin types (0xc4–0xc6), CBOR has byte strings (major type 2), and BSON has binary subtypes. When you transport images, hashes, vectors, or compressed blocks, this difference matters far more than saving a few punctuation bytes.
5. Side-by-side comparison
| Dimension | MessagePack | CBOR | BSON |
|---|---|---|---|
| Specification | Community spec | IETF RFC 8949 | MongoDB ecosystem |
| Media type | application/msgpack (not IANA-standard) |
application/cbor (IANA-registered) |
No general media type |
| Type system | Six JSON types plus bin and ext | Six JSON types plus bin, tags, and simple values | Broader: ObjectId, Decimal128, dates, regex, timestamps |
| Extension mechanism | Extension types (for example timestamp -1) | Tags (dates, bignums, embedded CBOR) | Fixed type codes, extensions by ecosystem convention |
| Deterministic encoding | No standard requirement | Explicit rules, usable for signatures | None |
| Self-describing | Yes, but no field-level schema | Yes, and describable with CDDL | Yes, with field names repeated per document |
| Small-document overhead | Low | Low | High (length prefix, field names, terminator) |
| Ecosystem | Caches, logs, some RPC | COSE, WebAuthn, IoT | MongoDB only |
6. Browser and Node support
Node 24 ships none of these codecs, so you install a library. Common choices:
| Runtime | MessagePack | CBOR | BSON |
|---|---|---|---|
| Node.js | @msgpack/msgpack, msgpackr |
cbor-x, borc |
bson |
| Browser | @msgpack/msgpack |
cbor-x browser build, cbor-web |
bson |
Both families work on Uint8Array and do not require Buffer. Watch the version differences:
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 };
}
Most libraries decode large integers to number by default and lose precision beyond the safe range. If exact values matter, enable the BigInt option explicitly (useBigInt64 in @msgpack/msgpack, an equivalent switch in cbor-x), otherwise you have simply moved JSON's old problem into a new format.
7. Debuggability and protocol compatibility
7.1 Debuggability: the largest hidden cost
The biggest advantage of text JSON is not size; it is that you can open it at any time. After switching to binary:
grep,awk, andjqall stop working, and log pipelines need an extra decode step;- Debugging production requires the right decoder at the right version, or the field names you see will be wrong;
- Captured traffic shows up in browser developer tools as an unreadable byte blob;
- Code review diffs show a string of hexadecimal.
CBOR has diagnostic notation, which renders bytes back into readable text close to JSON, for example a map as {1: 2}; MessagePack also has a few command-line decoders. But all of them require extra installation and sit outside the standard operations path.
The conclusion: whenever people need to inspect the data frequently, the hidden cost of a binary format exceeds the bandwidth it saves.
7.2 Protocol compatibility and extensions
- CBOR has the most complete tag mechanism: tag 0 for a date-time string, tag 1 for a Unix timestamp, tags 2 and 3 for bignums, and tag 24 for embedded CBOR. Combined with deterministic encoding, it is used by protocols that need signatures and cross-implementation consistency, such as COSE, WebAuthn, and CWT.
- MessagePack leaves extension types to implementers. In practice the only common one is timestamp (extension type -1). Across teams, every extension type must be written into the protocol document or the other side cannot decode it.
- BSON has fixed type codes, and
ObjectId,Decimal128,MinKey, andMaxKeyare MongoDB-specific semantics. Treating BSON as a general interchange format brings a pile of unused types and unused length prefixes.
If a protocol requires "the same data must produce the same bytes", only CBOR provides a standard answer; MessagePack and BSON both need extra agreement on field order and encoding details.
8. When it is worth switching
Signals that justify a switch:
- Payloads are large, both ends can change, and compressed sizes still differ meaningfully;
- You transport binary content and the Base64 inflation and decode cost are measurable;
- You need 64-bit integers or exact decimals that JSON doubles cannot represent;
- The link is server-to-server and diagnostic tooling can be standardised.
Signals that argue against it:
- gzip or brotli is already in place and the gain is a single-digit percentage;
- The API is public and third parties only have general-purpose HTTP clients;
- The data is logs or configuration that people read by hand;
- The team has no budget to maintain decoders and version compatibility.
A middle path also works: keep JSON externally and use MessagePack for internal caches. Cache values are read only by your own services, so both size and parse cost improve, and clearing the cache resolves any problem without touching the contract.
9. Checklist
- [ ] Compare compressed byte counts from real data, not uncompressed counts;
- [ ] Decide the big-integer policy: BigInt option, string transport, or CBOR bignums;
- [ ] Send binary fields as bin or byte strings instead of wrapping them in Base64;
- [ ] Write extension types and tags into the protocol document and pin decoder versions;
- [ ] Keep a text-JSON debug path that can be enabled during production incidents;
- [ ] Run a benchmark and record reproducible numbers instead of verbal claims.
What is the difference between MessagePack and CBOR?
Their encodings are very similar: maps, arrays, and strings all use a compact type-plus-length prefix. The main difference is standardisation. CBOR is an IETF RFC with an IANA media type, a tag mechanism, and deterministic encoding rules; MessagePack is a community specification whose extension types rely on private agreements. Choose CBOR when you need signatures, cross-implementation consistency, or constrained-device protocols.
Can BSON replace JSON as a general format?
It is not recommended. BSON was designed for database documents: every document carries a 4-byte total length, every field repeats its type and name, and small documents end up larger than JSON text. It also includes MongoDB-specific types such as ObjectId and MinKey. Outside MongoDB, most of its type system is unused while the size penalty remains.
How much smaller does a binary format make my payload?
It depends on the data shape. When field names repeat heavily, compressed text JSON and binary formats end up close; when names are short and the data is full of structural characters and numbers, the binary advantage is clearer. Measure real samples both uncompressed and after gzip, then decide, rather than estimating with a generic multiplier.
Can I still debug binary data with jq?
No. jq only processes text JSON and will fail immediately on MessagePack or CBOR bytes. You have to decode with the matching library into JSON first and then pass it to jq; CBOR can also go through a diagnostic-notation tool to become readable text. Keeping a text debug path is cheaper than installing tooling on demand.