JSON vs CBOR
JSON and CBOR represent the same data model – numbers, strings, booleans, null, arrays, and maps – but with fundamentally different encodings. JSON encodes everything as human-readable text, paying a size and parsing cost for readability. CBOR encodes data in a compact binary format where each item carries a 3-bit major type and 5-bit additional info in its first byte, eliminating field-name repetition overhead for well-known structures. CBOR is defined by RFC 8949 (December 2020, STD 94) and was specifically designed to be a binary JSON with a much smaller code footprint than Protobuf – suitable for embedded systems and IoT devices with 8-bit microcontrollers. The FIDO Alliance chose CBOR as the mandatory encoding for WebAuthn authenticator data, CTAP2 commands, and COSE (CBOR Object Signing and Encryption, RFC 8152).
JSON is a human-readable text format – universally supported, self-describing, easy to debug. CBOR (Concise Binary Object Representation) is the binary equivalent defined by RFC 8949. CBOR produces smaller payloads than equivalent JSON for typical API structures by eliminating string key repetition and encoding integers natively – the actual reduction depends on payload shape. CBOR parses faster with no string processing and supports 64-bit integers and byte strings natively. CBOR is the wire format for FIDO2/WebAuthn, CoAP, and constrained IoT devices.
| Feature | JSON | CBOR |
|---|---|---|
| Encoding | Text (UTF-8) | Binary (RFC 8949 initial byte + payload) |
| Human readable | Yes – readable without tools | No – requires cbor2, python-cbor, or hex dump |
| RFC | RFC 8259 (2017), ECMA-404 | RFC 8949 (2020), STD 94 |
| Payload size | Baseline | Smaller for typical API payloads – eliminates string key overhead and encodes integers natively. Actual reduction depends on payload structure: key-heavy objects benefit most, string-value-heavy payloads less so. |
| Integers | IEEE 754 double – max safe 2^53-1 | Native uint64/int64 – full 64-bit range without loss |
| Binary data | Base64 encode required (+33% size) | Native byte string (major type 2) – no encoding overhead |
| Schema required | No – self-describing | No – self-describing with major type tags |
| Type system | 6 types: string, number, boolean, null, array, object | 8 major types + semantic tags for dates, UUIDs, bignums |
| Streaming | Newline-delimited JSON or SSE | Indefinite-length sequences for streaming (§3.2.3) |
| Canonical form | No standard – key order not guaranteed | CTAP2 Canonical CBOR defined for deterministic encoding |
| Browser support | JSON.parse() native in all browsers | No native support – cbor-js or similar required |
| Primary use cases | REST APIs, config files, web data interchange | FIDO2/WebAuthn, CoAP (IoT), COSE, constrained devices |
When to use JSON
JSON is correct for: public REST APIs, browser-facing applications, any context where human readability and universal tooling matter. If you need third parties to consume your API without special libraries, if your payload contains mostly string data, or if debugging with curl/browser devtools is important – use JSON.
When to use CBOR
CBOR is correct for: FIDO2/WebAuthn implementations (mandatory by spec), CoAP-based IoT systems, applications that transmit binary data (certificates, keys, sensor readings) frequently, and any system on constrained hardware where JSON parsing overhead or base64 encoding costs are significant. CBOR is also the right choice when 64-bit integer precision is required without workarounds.
Common Mistakes
- Using JSON to transmit binary data like cryptographic keys or image thumbnails – base64 adds 33% overhead and parsing cost. CBOR byte strings eliminate both.
- Assuming CBOR requires a schema like Protobuf – CBOR is schema-free and self-describing. The major type in each initial byte identifies the value type without any external definition.
- Using JSON integers for FIDO2/WebAuthn credential IDs – the WebAuthn spec mandates CBOR encoding for authenticator data. Trying to use JSON here breaks compliance.
- Expecting universal CBOR tooling at the same level as JSON – CBOR library support is good (Python cbor2, Go github.com/fxamacker/cbor, Rust ciborium) but not as ubiquitous as JSON in every framework.
FAQ
Is CBOR a strict binary version of JSON?
CBOR supports the same six JSON value types (string, number, boolean, null, array, map) and adds native 64-bit integers, byte strings, semantic tags, and floating-point half-precision. Every JSON document can be losslessly converted to CBOR. Not every CBOR document can be converted to JSON – byte strings and 64-bit integers have no direct JSON equivalent.
Why did FIDO2 and WebAuthn choose CBOR over JSON?
WebAuthn authenticator data includes raw binary fields (credential IDs, public keys, attestation certificates) that are awkward in JSON. CBOR handles binary natively without base64. CBOR also has a well-defined canonical form (CTAP2 Canonical CBOR) required for deterministic signing. JSON has no canonical form standard, making it unsuitable for cryptographic applications where byte-identical encoding is required.
How much smaller is CBOR than JSON in practice?
For typical API payloads with string-heavy data, CBOR is 15–30% smaller. For payloads with binary data that would require base64 in JSON, CBOR can be 40–60% smaller. For integer-heavy payloads (sensor data, telemetry), the savings depend on value magnitude – small integers (0–23) encode in a single byte in CBOR.