jsonperformancemessagepackcboravro

MessagePack, CBOR & Avro: Binary JSON Alternatives Compared

·9 min read·Performance

JSON's biggest weakness is also its biggest strength

JSON is text — human-readable, universally supported, trivial to debug by opening a file. That same textual nature is exactly why it's inefficient at scale: every number is spelled out as digits instead of stored as raw bytes, every key name is repeated in full on every single record, and there's no way to distinguish an integer from a float without re-parsing the string. For a config file or a low-volume API, none of this matters. For a high-throughput message queue or a data lake with billions of records, it adds up to real latency and storage cost — which is exactly the gap MessagePack, CBOR, and Avro each fill, in different ways.

MessagePack: "JSON, but binary"

MessagePack is the most direct binary analog to JSON — same data model (objects, arrays, strings, numbers, booleans, null), just encoded as compact bytes instead of text, with no schema required:

json
{ "name": "Ada", "age": 36, "active": true }
text
JSON (text):       43 bytes: {"name":"Ada","age":36,"active":true}
MessagePack (hex):  ~24 bytes: 83 a4 6e 61 6d 65 a3 41 64 61 a3 61 67 65 18 a6 61 63 74 69 76 65 c3

The savings come from small, mechanical wins: a string's length is stored as a byte prefix rather than needing a closing quote to scan for, small integers pack into a single byte instead of ASCII digits, and there's zero whitespace. Because it's schemaless like JSON, it's a near drop-in replacement wherever you already pass around dynamically-shaped objects:

javascript
// npm install @msgpack/msgpack
import { encode, decode } from "@msgpack/msgpack";

const packed = encode({ name: "Ada", age: 36, active: true });
const obj = decode(packed); // { name: "Ada", age: 36, active: true }

Redis, RabbitMQ, and several RPC frameworks support MessagePack natively as a faster wire format for exactly this reason — it needs no schema registry or code generation step, just a library on each end.

CBOR: the IETF standard, built for constrained devices

CBOR (Concise Binary Object Representation, RFC 8949) solves largely the same problem as MessagePack but as a formal IETF internet standard, with particular attention to extremely resource-constrained environments — IoT sensors, embedded devices, low-power network protocols:

javascript
// npm install cbor
import { encode, decode } from "cbor";

const packed = encode({ temperature: 22.5, sensorId: "s-042" });
const obj = decode(packed);

CBOR is the encoding underneath WebAuthn (the passkey/security-key standard) and COSE (CBOR Object Signing and Encryption, used in some verifiable-credential systems) — if you've implemented passwordless login, you've already shipped CBOR without necessarily noticing, since browser WebAuthn APIs handle the encoding for you.

Avro: schema-first, built for big data pipelines

Avro takes a fundamentally different approach: instead of being schemaless like JSON/MessagePack/CBOR, every Avro file or message is paired with an explicit schema, and the encoded bytes contain almost no field names at all — just the values, in the exact order the schema defines:

json
// The schema (defines field names, types and order — stored once)
{
  "type": "record",
  "name": "User",
  "fields": [
    { "name": "name", "type": "string" },
    { "name": "age", "type": "int" },
    { "name": "active", "type": "boolean" }
  ]
}

Because the schema already declares that field 1 is a string, field 2 an int, and field 3 a boolean, the encoded message for {"name": "Ada", "age": 36, "active": true} never needs to repeat the strings "name", "age", or "active" at all — dramatically smaller than JSON or even MessagePack/CBOR once you have many records sharing one schema, which is exactly the case in a Kafka topic or a data warehouse table with millions of rows.

Avro also has a defining feature the other formats don't: schema evolution rules — you can add a field with a default value, or remove one, and old and new schema versions remain compatible, which is why Avro (alongside Protobuf) dominates as the serialization format inside Kafka-based data pipelines and data lakes.

Side-by-side comparison

FormatSchema required?Typical size vs JSONBest for
JSONNoBaselineAPIs, config, human-readable debugging
MessagePackNo~40–60% smallerDrop-in JSON replacement, caches, RPC
CBORNo~40–60% smallerIoT, embedded, WebAuthn/COSE
AvroYes~70–90% smaller (many records, shared schema)Kafka topics, data lakes, big data pipelines
Protobuf*Yes~70–90% smallergRPC, cross-language typed APIs

\*See Protobuf vs JSON: When to Switch for a deeper comparison specifically against Protobuf, which shares Avro's schema-first philosophy but targets RPC APIs rather than data pipelines.

Choosing between them

The decision mostly comes down to one question: do you already have (or want) a shared schema? If your data is genuinely dynamic and schemaless — arbitrary JSON blobs, user-defined structures — MessagePack or CBOR are easy wins with almost no design cost. If you're building a data pipeline where every record shares a known, evolving structure (a Kafka topic, a data warehouse table), Avro's schema-driven compactness and built-in evolution rules are worth the upfront schema design.

text
Schemaless, need speed/size over JSON  → MessagePack (general) or CBOR (IoT/standards-compliant)
Schema-first, high-volume pipeline     → Avro (or Protobuf for RPC specifically)
Human-readable, low volume, debugging  → Plain JSON — don't over-engineer this

Debugging binary formats (the part JSON makes trivial)

The single biggest cost of any binary format is that you can no longer just open the file and read it — every debugging step needs a decode step first. A practical workflow: decode the binary payload back to JSON at the point where you need to inspect it, then use ordinary JSON tooling from there:

javascript
import { decode } from "@msgpack/msgpack";
console.log(JSON.stringify(decode(packedBuffer), null, 2));

Paste that decoded JSON into JSONKit's JSON Formatter to read a large or deeply nested payload, or the JSON Validator to confirm a decoded structure matches what you expect before debugging further upstream.

Frequently asked questions

Almost always for typical data, though the savings are modest (roughly 40-60%) since it shares JSON's schemaless, field-name-repeated-per-record design. For genuinely dramatic size reductions on repetitive records, a schema-based format like Avro or Protobuf wins by far more.

CBOR is a ratified IETF standard with a formal spec (RFC 8949) and is the required encoding for specific standards like WebAuthn/COSE — if you're implementing one of those, CBOR isn't really optional. Outside of that, the practical difference between CBOR and MessagePack for your own data is minor.

Not necessarily — Avro's main advantages (extreme compactness via a shared schema, formal schema evolution) matter most when you have high-volume, repetitive records. A low-traffic REST API rarely benefits enough to justify the added schema-management overhead.

Yes for MessagePack and CBOR, which share JSON's data model almost exactly. Avro requires mapping JSON into its schema's types first — numbers need to match a specific declared type (int vs long vs float), which is the main friction point when adopting it.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.