Compact, Lossless JSON Output for LLM Agents: How Much Do You Actually Save?
When you hand scraped data to an LLM as an array of JSON objects, every row repeats
every key name. For a 400-row export that's thousands of tokens spent on
"title", "view_count" and "author_name" over and
over before the model ever sees a single value. We measured how much that actually
costs, and built a lossless alternative into five of our actors.
The fix: columnar, but still lossless
Instead of an array of objects, send one header listing the columns once, then one array per row:
{"format":"columnar-v1","columns":["id","title","views"],"missing":"¤",
"rows":[["BV1xx","2026 keyboard roundup",12034],["BV2yy","¤",877]]}
A row that lacks a key gets a reserved marker ("¤" by default, and
the encoder picks a different one automatically if that symbol ever collides with real
data) instead of being padded with null — so "this field doesn't exist
on this row" is never confused with "this field is explicitly null". Decoding is five
lines of Python and gets back the exact original rows:
def decode_columnar(payload):
cols, marker = payload["columns"], payload["missing"]
return [{c: v for c, v in zip(cols, row) if v != marker}
for row in payload["rows"]]
Results: 882 real rows, five actors
We exported real rows from five actors that share this China-region scraper codebase
and measured GPT-4o's o200k_base tokenizer against pretty-printed JSON
(indent=2), minified JSON, and the columnar format above:
| Actor | Rows | vs pretty JSON | vs minified JSON |
|---|---|---|---|
| AliExpress Scraper | 130 | 43.1% smaller | 25.6% smaller |
| Bilibili Scraper | 406 | 41.9% smaller | 25.9% smaller |
| Douban Scraper | 150 | 39.6% smaller | 22.3% smaller |
| Weibo Scraper | 119 | 24.8% smaller | 4.9% smaller |
| WeChat Articles Scraper | 77 | 12.8% smaller | 6.2% smaller |
| All five combined | 882 | ~34% smaller | ~18% smaller |
Savings range from 13% to 43% depending on the actor, averaging about 34% across all 882 rows versus pretty JSON. The pattern is consistent: more fields, more rows, and shorter values mean a bigger win, because the repeated key names are a larger share of the payload. WeChat Articles gains the least because each row is mostly unique article text — key names are already a small fraction of the bytes. Weibo gains less than Bilibili/Douban/AliExpress because its rows mix two very different shapes (22-key posts alongside 10-key comments), which forces the "missing" marker into the shorter rows.
Why not a smarter (lossy) compressor?
We also benchmarked an AI-oriented compression library (headroom) on the same files. It reached higher headline compression on paper, but it dropped rows or fields on 7 of the 8 sample datasets we tested it against. That's an acceptable tradeoff for an agent's disposable scratch memory, but not for data a customer is paying us to deliver intact. The columnar format above is the one we shipped because every row we promise you is a row you actually get back, byte for byte, after decoding.
Try it
Set "outputFormat": "compact" on any run of these actors. The run still
pushes the same rows to the dataset at the same price — billing is unaffected. The
actor additionally writes an OUTPUT_COMPACT key to the run's key-value
store with the columnar JSON above, which is cheaper to hand to an LLM than the plain
dataset export: