I Wrote Multiple CUE Parsers and Benchmarked Them Against JSON

It started yesterday because I wanted to fix JSON.

Well, not fix exactly. More like figure out if there was something better for client-side parsing in the browser. I’d been looking at comparing JSON versus MessagePack when I stumbled into CUE, a configuration language that embeds schema definitions directly in the data.

I posted about it and then was like well what if I just actually did it. There was just one problem: the core CUE spec is written in Go, which obviously doesn’t help when you need something running in React.

So I wrote a TypeScript parser for CUE. You can find it at cue-ts.

Then I built a benchmark app with 11 different parsing and deserialization approaches, all running client-side in a Vite + React app. We’ll see how that turned out here.

The Contenders

I tested across three ecosystems:

JSON: Native JSON.parse, JSON + Zod validation, and JSON + Ajv (both pre-compiled and per-call schema compilation).

CUE: Full AST parsing, a TypeScript deserializer, a fused single-pass scanner, pre-compiled CUE schemas, and even a Rust-to-WASM CUE parser.

Binary: MessagePack decoding, with and without Zod validation.

Payloads ranged from ~1KB to ~460KB across CUE, JSON, and MessagePack formats.

Understanding the Schema Problem

Before we look at numbers, we need to talk about schema validation.

With Zod, your schema is TypeScript code. It’s set at compile time, so there’s no runtime schema parsing overhead. With Ajv in compiled mode, it takes a JSON Schema document and generates an optimized JavaScript validation function. You do this once, and the runtime cost is essentially zero.

But CUE does something different. It embeds schema definitions directly in the data file:

#User: {
    email: string & =~"^[^@]+@[^@]+$"
    role:  "admin" | "editor" | "viewer"
    age:   int & >=0
}

user: #User & {
    email: "[email protected]"
    role:  "admin"
    age:   30
}

Schema and data together, processed in one pass. Sounds elegant, right? The question is whether that elegance costs you speed. 😅

The Results

Here’s the 10KB payload benchmark on Chromium/V8:

Strategy Median vs JSON.parse
JSON + Ajv (compiled) 18 μs ~equivalent
JSON.parse 18 μs baseline
JSON + Zod 28 μs 1.6x slower
MsgPack Decode 30 μs 1.7x slower
CUE (compiled schema) 112 μs 6.2x slower
CUE Fast Deserialize 114 μs 6.3x slower
CUE Deserialize (WASM) 755 μs 41.9x slower

JSON + Ajv compiled is equivalent to bare JSON.parse. The pre-compiled validator adds essentially zero overhead. Case closed?

Not quite.

The Fair Fight

Those benchmarks aren’t comparing the same thing. Zod and Ajv compiled both pre-process their schemas before the benchmark runs. CUE processes its schema on every call. So I ran the fair comparison, schema processing included:

Strategy Median
CUE Fast Deserialize 114 μs
CUE (compiled schema) 112 μs
JSON + Ajv (interpret) 4,840 μs

When JSON has to compile its schema per call, CUE is 43x faster. Cool? All that work really paid off?

Well, the bottleneck for CUE isn’t schema processing. It’s parsing the text format itself. I tried basically every optimization I could think of. The deserializer spends nearly all its time scanning characters, building strings and numbers. Without native C++ code, which is what JSON.parse gets for free from the browser engine, a TypeScript parser just can’t close that gap.

MessagePack: Not Worth It

I included MessagePack benchmarks to see if smaller binary payloads would translate to faster parsing. They don’t. MessagePack is consistently slower than JSON.parse, even with smaller payloads. The overhead of maintaining a separate binary serialization format on the client side just isn’t worth it for most use cases.

Cross-Browser Highlights

I ran a few tests across engines. Some notable findings:

  • Safari (JSC): Zod validation is essentially free. JSON + Zod matches bare JSON.parse
  • Chrome (V8): CUE’s fast deserializer benefits from charCodeAt optimizations
  • Firefox (SpiderMonkey): Most consistent results across the board

So What’s the Answer?

If you can compile your schema ahead of time with Ajv or Zod, do that and use JSON.

It’s not even close. Seriously.

JSON.parse benefits from decades of native browser optimization that no TypeScript parser can match, and pre-compiled validation adds essentially zero overhead.

CUE is faster in one specific scenario: when you need to process schemas dynamically on every call. 43x faster than per-call Ajv compilation is real, but how often does that actually come up in production? It’s hard to say.

Use Case Pick This Why
Hot-path APIs JSON + Ajv (compiled) Fastest possible
TypeScript projects JSON + Zod Best DX
Dynamic schemas CUE 43x faster than per-call Ajv
Config files CUE Single source of truth
Bandwidth-constrained MsgPack 30-60% smaller payloads

One thing I considered but didn’t build for this post: a conversion layer that stores everything as CUE on the server, then compiles it down to JSON with Zod or Ajv schemas for the browser. You’d get CUE’s authoring experience with JSON’s runtime speed. That feels like an interesting project, and maybe I’ll get to it next.

Both the cue-ts parser and the benchmark app are open source. Try them yourself and let me know what you think.

/ Programming / Webdev / javascript / Benchmarks