jalapenojson

Benchmark

JSON against jalapenojson: the same rows, read and written by each, in JavaScript, Python, C and WebAssembly. Every table has the same shape: JSON in one column, jalapenojson in the next, and how many times faster or slower jalapenojson is.

Measured on 2026-10-03 at commit 2eb975b: Intel Xeon Processor @ 2.10GHz, 4 cores, Linux 6.18.44-fc-v64; Node 24.21.0, Python 3.11.15, cc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 with -O2, cJSON 1.7.19, yyjson 0.13.0. Reproduce with bench/run.sh.

The data

Two sets of rows with the same seven columns: id (i), user, city and plan (s), seats (i), price (f) and joined (d). They are never averaged, because they disagree about size.

  • repetitive is the shape API output usually has: a user drawn from two thousand, a city from eight (one of them Tromsø, so the text is not pure ASCII), a plan from four, a price from a price list.
  • high-entropy fills the same columns with random letters and numbers, so there is little for a compressor to find in either format.
  • nested orders, orders carrying one to six line items each, is measured for size only.

bench/data.mjs writes each set once, as JSON and as jalapenojson, and every language reads those same files.

Size

Raw, then compressed the way a server would send it: gzip at level 6, brotli at quality 5 (compressed as it is sent) and 11 (compressed once, ahead of time).

50,000 rowsJSONjalapenojsondifference
repetitive, raw5.55 MB2.75 MB50% smaller
repetitive, gzip609 KB533 KB13% smaller
repetitive, brotli 5609 KB490 KB20% smaller
repetitive, brotli 11460 KB403 KB12% smaller
high-entropy, raw6.27 MB3.50 MB44% smaller
high-entropy, gzip2.27 MB2.09 MB8% smaller
high-entropy, brotli 52.20 MB2.02 MB8% smaller
high-entropy, brotli 112.01 MB1.88 MB6% smaller
nested orders, raw12.04 MB5.22 MB57% smaller
nested orders, gzip1.78 MB1.54 MB14% smaller
nested orders, brotli 51.72 MB1.53 MB11% smaller
nested orders, brotli 111.39 MB1.25 MB10% smaller

JavaScript

JSON.parse and JSON.stringify against decode(), view() and encode(), on Node 24.21.0. Warmed up: the fastest run once the reader has run for 200 ms, which is what a server or a page that reads many documents sees.

  • read every value builds every row as an object: JSON.parse against decode().
  • read every value, as columns ends with one array per column: JSON.parse and a pass per column, against view() and column().
  • read one number column, read one text column and read one row end with the values of price, of city, or of the middle row: JSON.parse and a pass, against view() and column() or row().
  • write every value turns the rows each reader built back into bytes: JSON.stringify and a TextEncoder, against encode().
50,000 rowsdataJSONjalapenojsondifference
read every valuerepetitive24.4 ms20.9 ms1.2x faster
high-entropy31.2 ms17.2 ms1.8x faster
read every value, as columnsrepetitive32.6 ms17.3 ms1.9x faster
high-entropy39.4 ms14.5 ms2.7x faster
read one number columnrepetitive25.3 ms1.69 ms15x faster
high-entropy31.7 ms1.63 ms19x faster
read one text columnrepetitive26.0 ms4.92 ms5.3x faster
high-entropy33.3 ms2.38 ms14x faster
read one rowrepetitive23.6 ms0.104 ms228x faster
high-entropy30.8 ms0.122 ms252x faster
write every valuerepetitive22.3 ms15.6 ms1.4x faster
high-entropy22.1 ms26.2 ms1.2x slower

JSON's time includes turning the bytes into the string JSON.parse takes, because jalapenojson's includes reading the same bytes. Handed a string it already has, JSON.parse alone takes 20.9 ms on the repetitive rows and 26.8 ms on the high-entropy rows at 50,000 rows.

The first call

A page that reads one document reads it once, in a process that has never run the reader: V8 compiles a JavaScript reader as it goes, while JSON.parse is already machine code. Each number is the median of one run in each of several fresh processes.

1,000 rows, first calldataJSONjalapenojsondifference
read every valuerepetitive0.993 ms4.85 ms4.9x slower
high-entropy1.19 ms3.85 ms3.2x slower
read one number columnrepetitive1.11 ms1.77 ms1.6x slower
high-entropy1.41 ms1.88 ms1.3x slower
read one text columnrepetitive1.12 ms2.53 ms2.3x slower
high-entropy1.22 ms1.80 ms1.5x slower
read one rowrepetitive1.04 ms1.34 ms1.3x slower
high-entropy1.12 ms1.35 ms1.2x slower
50,000 rows, first calldataJSONjalapenojsondifference
read every valuerepetitive42.9 ms51.4 ms1.2x slower
high-entropy60.5 ms42.3 ms1.4x faster
read one number columnrepetitive46.1 ms9.75 ms4.7x faster
high-entropy58.3 ms11.2 ms5.2x faster
read one text columnrepetitive44.4 ms18.7 ms2.4x faster
high-entropy57.5 ms12.4 ms4.6x faster
read one rowrepetitive50.0 ms2.50 ms20x faster
high-entropy55.3 ms2.54 ms22x faster

1,000 rows

1,000 rowsdataJSONjalapenojsondifference
read every valuerepetitive0.422 ms0.337 ms1.3x faster
high-entropy0.422 ms0.289 ms1.5x faster
read every value, as columnsrepetitive0.527 ms0.232 ms2.3x faster
high-entropy0.540 ms0.187 ms2.9x faster
read one number columnrepetitive0.428 ms0.026 ms17x faster
high-entropy0.423 ms0.030 ms14x faster
read one text columnrepetitive0.422 ms0.056 ms7.6x faster
high-entropy0.422 ms0.024 ms17x faster
read one rowrepetitive0.419 ms0.006 ms67x faster
high-entropy0.416 ms0.007 ms63x faster
write every valuerepetitive0.325 ms0.310 msabout the same
high-entropy0.313 ms0.399 ms1.3x slower

In a browser

Everything above starts with the bytes in memory. A page has to receive them first. Chromium 141.0.7390.37, driven by Playwright, fetches each document from a local server that sends it gzipped, over a connection DevTools slows to a given speed and latency, and then reads it: what a page that fetches one document waits for. The download is the fastest of three; the read is the first one a fresh page does, the median of several pages.

50,000 rows, fast 4G (9 Mbit/s, 85 ms)dataJSONjalapenojsondifference
receive the documentrepetitive722 ms577 ms1.3x faster
high-entropy2,139 ms1,971 ms1.1x faster
receive it and read every valuerepetitive751 ms622 ms1.2x faster
high-entropy2,173 ms2,012 ms1.1x faster
receive it and read one number columnrepetitive753 ms583 ms1.3x faster
high-entropy2,177 ms1,977 ms1.1x faster
receive it and read one rowrepetitive753 ms580 ms1.3x faster
high-entropy2,173 ms1,973 ms1.1x faster
50,000 rows, slow link (1.6 Mbit/s, 150 ms)dataJSONjalapenojsondifference
receive the documentrepetitive3,226 ms2,834 ms1.1x faster
high-entropy11,543 ms10,611 ms1.1x faster
receive it and read every valuerepetitive3,255 ms2,878 ms1.1x faster
high-entropy11,577 ms10,652 ms1.1x faster
receive it and read one number columnrepetitive3,257 ms2,840 ms1.1x faster
high-entropy11,581 ms10,617 ms1.1x faster
receive it and read one rowrepetitive3,256 ms2,836 ms1.1x faster
high-entropy11,577 ms10,613 ms1.1x faster

At 50,000 rows on fast 4G, reading every value is 4% with JSON and 7% with jalapenojson on the repetitive rows, and 2% with JSON and 2% with jalapenojson on the high-entropy rows, of what the page waits for. The rest is the download.

1,000 rows, fast 4G (9 Mbit/s, 85 ms)dataJSONjalapenojsondifference
receive the documentrepetitive105 ms102 msabout the same
high-entropy130 ms126 msabout the same
receive it and read every valuerepetitive106 ms106 msabout the same
high-entropy130 ms130 msabout the same
receive it and read one number columnrepetitive106 ms103 msabout the same
high-entropy131 ms128 msabout the same
receive it and read one rowrepetitive106 ms103 msabout the same
high-entropy130 ms128 msabout the same
1,000 rows, slow link (1.6 Mbit/s, 150 ms)dataJSONjalapenojsondifference
receive the documentrepetitive217 ms209 msabout the same
high-entropy382 ms367 msabout the same
receive it and read every valuerepetitive217 ms213 msabout the same
high-entropy383 ms370 msabout the same
receive it and read one number columnrepetitive217 ms211 msabout the same
high-entropy383 ms369 msabout the same
receive it and read one rowrepetitive217 ms210 msabout the same
high-entropy383 ms368 msabout the same

At 1,000 rows on fast 4G, reading every value is under 1% with JSON and 4% with jalapenojson on the repetitive rows, and under 1% with JSON and 3% with jalapenojson on the high-entropy rows, of what the page waits for. The rest is the download.

Python

json.loads and json.dumps against decode(), view() and encode(), on Python 3.11.15. The same tasks, warmed up the same way. Python's json is written in C; jalapenojson's Python reader is Python. JSON is written compact and as UTF-8, the way the file it was read from was.

50,000 rowsdataJSONjalapenojsondifference
read every valuerepetitive56.8 ms50.4 ms1.1x faster
high-entropy54.8 ms57.4 msabout the same
read every value, as columnsrepetitive74.8 ms33.5 ms2.2x faster
high-entropy66.0 ms39.3 ms1.7x faster
read one number columnrepetitive50.8 ms7.66 ms6.6x faster
high-entropy44.9 ms7.30 ms6.1x faster
read one text columnrepetitive54.8 ms6.47 ms8.5x faster
high-entropy50.0 ms6.02 ms8.3x faster
read one rowrepetitive59.0 ms0.099 ms596x faster
high-entropy52.6 ms0.124 ms425x faster
write every valuerepetitive81.2 ms57.9 ms1.4x faster
high-entropy86.0 ms64.4 ms1.3x faster

1,000 rows

1,000 rowsdataJSONjalapenojsondifference
read every valuerepetitive0.855 ms0.789 ms1.1x faster
high-entropy0.752 ms0.863 ms1.1x slower
read every value, as columnsrepetitive1.00 ms0.572 ms1.7x faster
high-entropy0.918 ms0.645 ms1.4x faster
read one number columnrepetitive0.889 ms0.138 ms6.5x faster
high-entropy0.772 ms0.141 ms5.5x faster
read one text columnrepetitive0.856 ms0.098 ms8.7x faster
high-entropy0.766 ms0.089 ms8.6x faster
read one rowrepetitive0.856 ms0.018 ms47x faster
high-entropy0.749 ms0.018 ms41x faster
write every valuerepetitive1.17 ms1.07 ms1.1x faster
high-entropy1.34 ms1.29 msabout the same

C

cJSON, the library most C code uses, and yyjson, one of the fastest JSON parsers there is, against jj_parse() and the accessors. Each run parses and then reads what the task asks for: a number as a double, checked against its grammar as the JSON parsers check it, and text as a pointer and a length. There is no C encoder, so there is no write task.

50,000 rowsdatacJSONyyjsonjalapenojsonvs cJSONvs yyjson
read every valuerepetitive49.7 ms6.03 ms4.10 ms12x faster1.5x faster
high-entropy54.6 ms6.79 ms3.87 ms14x faster1.8x faster
read one number columnrepetitive48.0 ms5.72 ms1.13 ms43x faster5.1x faster
high-entropy49.9 ms6.63 ms1.16 ms43x faster5.7x faster
read one text columnrepetitive47.7 ms5.73 ms0.407 ms117x faster14x faster
high-entropy47.9 ms6.50 ms0.422 ms114x faster15x faster
read one rowrepetitive48.1 ms5.87 ms0.059 ms820x faster100x faster
high-entropy48.6 ms6.54 ms0.085 ms574x faster77x faster

1,000 rows

1,000 rowsdatacJSONyyjsonjalapenojsonvs cJSONvs yyjson
read every valuerepetitive0.560 ms0.092 ms0.071 ms7.9x faster1.3x faster
high-entropy0.597 ms0.092 ms0.069 ms8.6x faster1.3x faster
read one number columnrepetitive0.554 ms0.081 ms0.017 ms33x faster4.8x faster
high-entropy0.594 ms0.084 ms0.020 ms29x faster4.1x faster
read one text columnrepetitive0.548 ms0.078 ms0.008 ms71x faster10x faster
high-entropy0.585 ms0.081 ms0.007 ms85x faster12x faster
read one rowrepetitive0.535 ms0.073 ms0.001 ms733x faster100x faster
high-entropy0.575 ms0.078 ms0.001 ms776x faster105x faster

WebAssembly

jalapenojson's C reader compiled to WebAssembly. Ubuntu clang version 18.1.3 (1ubuntu1) built jalapenojson.h for wasm32-wasi with -O2, against wasi-libc, into a module of 34 KB, 13 KB gzipped. Two questions, measured apart: whether reading through it is faster than the JavaScript reader, and whether jalapenojson's lead over the JSON parsers survives the move to WebAssembly.

WebAssembly cannot make a JavaScript string or object, so bench/wasm.c converts each number and copies each text value into one run of bytes in the module's memory, and bench/wasm.mjs turns what it leaves into the values the JavaScript reader hands back: the run of text is decoded once and each value cut out of it, and the rows are built in JavaScript. Every call copies the document into the module's memory first, because that is the only memory WebAssembly reads, and the copy is timed. The tasks are the JavaScript section's, but for writing, on Node 24.21.0, warmed up the same way.

50,000 rowsdataJSONJavaScriptWebAssemblyvs JSONvs JavaScript
read every valuerepetitive24.4 ms20.9 ms20.1 ms1.2x fasterabout the same
high-entropy31.2 ms17.2 ms20.8 ms1.5x faster1.2x slower
read every value, as columnsrepetitive32.6 ms17.3 ms13.6 ms2.4x faster1.3x faster
high-entropy39.4 ms14.5 ms17.1 ms2.3x faster1.2x slower
read one number columnrepetitive25.3 ms1.69 ms1.66 ms15x fasterabout the same
high-entropy31.7 ms1.63 ms1.71 ms18x faster1.1x slower
read one text columnrepetitive26.0 ms4.92 ms3.17 ms8.2x faster1.6x faster
high-entropy33.3 ms2.38 ms3.44 ms9.7x faster1.4x slower
read one rowrepetitive23.6 ms0.104 ms0.267 ms88x faster2.6x slower
high-entropy30.8 ms0.122 ms0.341 ms91x faster2.8x slower

The first call

One run in a fresh process, the median of several, as for the JavaScript reader. The module is compiled and instantiated before the clock starts, as the JavaScript reader is imported before it starts, so what a first call pays for is what V8 compiles as it goes, in either.

1,000 rows, first calldataJSONJavaScriptWebAssemblyvs JSONvs JavaScript
read every valuerepetitive0.993 ms4.85 ms2.53 ms2.5x slower1.9x faster
high-entropy1.19 ms3.85 ms2.59 ms2.2x slower1.5x faster
read one number columnrepetitive1.11 ms1.77 ms1.32 ms1.2x slower1.3x faster
high-entropy1.41 ms1.88 ms1.24 ms1.1x faster1.5x faster
read one text columnrepetitive1.12 ms2.53 ms1.69 ms1.5x slower1.5x faster
high-entropy1.22 ms1.80 ms1.20 msabout the same1.5x faster
read one rowrepetitive1.04 ms1.34 ms1.31 ms1.3x slowerabout the same
high-entropy1.12 ms1.35 ms1.55 ms1.4x slower1.1x slower
50,000 rows, first calldataJSONJavaScriptWebAssemblyvs JSONvs JavaScript
read every valuerepetitive42.9 ms51.4 ms44.9 msabout the same1.1x faster
high-entropy60.5 ms42.3 ms46.6 ms1.3x faster1.1x slower
read one number columnrepetitive46.1 ms9.75 ms7.57 ms6.1x faster1.3x faster
high-entropy58.3 ms11.2 ms8.47 ms6.9x faster1.3x faster
read one text columnrepetitive44.4 ms18.7 ms10.5 ms4.2x faster1.8x faster
high-entropy57.5 ms12.4 ms13.6 ms4.2x faster1.1x slower
read one rowrepetitive50.0 ms2.50 ms3.14 ms16x faster1.3x slower
high-entropy55.3 ms2.54 ms3.40 ms16x faster1.3x slower

In a browser

The first read in a fresh page of Chromium 141.0.7390.37, the median of several pages, as in the browser section. The document is the one the JavaScript reader reads, so it downloads in the same time, and only the read is shown. Fetching, compiling and instantiating the module took 3.23 ms in a fresh page, the median over those pages, which a page does once and can do while the document downloads.

50,000 rows, first read in a pagedataJSONJavaScriptWebAssemblyvs JSONvs JavaScript
read every valuerepetitive29.2 ms44.5 ms41.3 ms1.4x slower1.1x faster
high-entropy33.9 ms41.4 ms42.3 ms1.2x slowerabout the same
read one number columnrepetitive31.2 ms5.73 ms6.70 ms4.7x faster1.2x slower
high-entropy37.4 ms6.29 ms7.71 ms4.9x faster1.2x slower
read one rowrepetitive30.5 ms2.38 ms2.76 ms11x faster1.2x slower
high-entropy33.5 ms2.44 ms2.97 ms11x faster1.2x slower
1,000 rows, first read in a pagedataJSONJavaScriptWebAssemblyvs JSONvs JavaScript
read every valuerepetitive0.615 ms4.36 ms2.46 ms4.0x slower1.8x faster
high-entropy0.615 ms3.79 ms2.42 ms3.9x slower1.6x faster
read one number columnrepetitive0.725 ms1.70 ms1.17 ms1.6x slower1.5x faster
high-entropy0.760 ms2.10 ms1.12 ms1.5x slower1.9x faster
read one rowrepetitive0.705 ms1.30 ms1.22 ms1.7x slower1.1x faster
high-entropy0.645 ms1.27 ms1.23 ms1.9x slowerabout the same

Inside WebAssembly

The C section's measurement compiled whole: bench/c.c, with cJSON and yyjson, built for wasm32-wasi the same way and run by Node's WASI. Numbers and text stay inside the module, as they stay inside the process in C, so nothing crosses into JavaScript, and this is WebAssembly's own speed.

50,000 rowsdatacJSONyyjsonjalapenojsonvs cJSONvs yyjson
read every valuerepetitive37.7 ms8.17 ms5.94 ms6.3x faster1.4x faster
high-entropy50.8 ms8.39 ms7.15 ms7.1x faster1.2x faster
read one number columnrepetitive37.8 ms7.93 ms1.19 ms32x faster6.7x faster
high-entropy47.8 ms8.23 ms1.25 ms38x faster6.6x faster
read one text columnrepetitive36.6 ms7.65 ms1.13 ms32x faster6.8x faster
high-entropy47.8 ms8.10 ms1.37 ms35x faster5.9x faster
read one rowrepetitive35.7 ms7.16 ms0.060 ms599x faster120x faster
high-entropy47.8 ms7.65 ms0.087 ms550x faster88x faster

Against the same code built natively, in the C section, a run in WebAssembly takes this many times as long at 50,000 rows: 0.7x to 1.0x with cJSON, 1.2x to 1.4x with yyjson, and 1.0x to 3.2x with jalapenojson.

1,000 rows

1,000 rowsdataJSONJavaScriptWebAssemblyvs JSONvs JavaScript
read every valuerepetitive0.422 ms0.337 ms0.324 ms1.3x fasterabout the same
high-entropy0.422 ms0.289 ms0.346 ms1.2x faster1.2x slower
read every value, as columnsrepetitive0.527 ms0.232 ms0.195 ms2.7x faster1.2x faster
high-entropy0.540 ms0.187 ms0.225 ms2.4x faster1.2x slower
read one number columnrepetitive0.428 ms0.026 ms0.021 ms20x faster1.2x faster
high-entropy0.423 ms0.030 ms0.026 ms16x faster1.2x faster
read one text columnrepetitive0.422 ms0.056 ms0.034 ms12x faster1.7x faster
high-entropy0.422 ms0.024 ms0.029 ms15x faster1.2x slower
read one rowrepetitive0.419 ms0.006 ms0.005 ms91x faster1.4x faster
high-entropy0.416 ms0.007 ms0.006 ms73x faster1.1x faster
1,000 rowsdatacJSONyyjsonjalapenojsonvs cJSONvs yyjson
read every valuerepetitive0.634 ms0.141 ms0.099 ms6.4x faster1.4x faster
high-entropy0.832 ms0.141 ms0.111 ms7.5x faster1.3x faster
read one number columnrepetitive0.619 ms0.137 ms0.016 ms38x faster8.5x faster
high-entropy0.810 ms0.136 ms0.021 ms38x faster6.4x faster
read one text columnrepetitive0.613 ms0.127 ms0.010 ms63x faster13x faster
high-entropy0.809 ms0.130 ms0.010 ms80x faster13x faster
read one rowrepetitive0.612 ms0.123 ms0.001 ms714x faster143x faster
high-entropy0.805 ms0.126 ms0.001 ms918x faster143x faster

How it was measured

  • Each number comes from its own process, started for that one measurement, so no reader runs in a process another reader has warmed.
  • Warmed up means the task ran until it had run for 200 ms, and then the fastest of at least 15 more runs and at least 500 ms was kept. The number shown is the median of that over 5 rounds, each of which ran every measurement once, in a different order.
  • Every run's result is checked: a count of the values it read, their sum, and a CRC-32 of their text, against the same worked out from the rows the files were written from. A run that read something else stops the benchmark.
  • The input is bytes in memory for both formats. Freeing memory is not timed in C; in JavaScript and Python the garbage collector runs when it runs.
  • In JavaScript and Python jalapenojson checks every date it reads, and Python gets date objects; JSON has no dates, so its readers hand the same values back as unchecked strings. In C every reader hands a date back as text, and so does the WebAssembly reader, having checked it.
  • The WebAssembly modules are built by bench/run.sh on every run, from bench/wasm.c and bench/c.c, and are measured by the same workers and checked against the same checksums as everything else.