The token-efficient replacement for JSON in LLM prompts. Cut ~60% of the characters you spend on nested JSON payloads. Lossless. Human-readable. Zero runtime dependencies. Ships for Python and TypeScript.
LLM tokens are money. A single call to GPT-5 or Claude Sonnet 4.6 with a nested JSON tool response can burn thousands of tokens repeating the same keys on every object in a list:
[{"id":1,"customer":{"name":"Ada","city":"Boulder"}},
{"id":2,"customer":{"name":"Linus","city":"Helsinki"}},
{"id":3,"customer":{"name":"Grace","city":"Baltimore"}}, ...]Every "id", "customer", "name", "city" is billed on every row. Multiply by 100 rows and half your context window is punctuation and duplicated keys.
SOON declares the shape once, then streams positional values. Same data, same information, smaller bill:
SHAPE customers = {id,customer:{name,city}}
customers[3]<customers>:
(1,(Ada,Boulder))
(2,(Linus,Helsinki))
(3,(Grace,Baltimore))
That's the whole idea. Everything else in this repo — the spec, the two implementations, the benchmarks, the conformance suite — is scaffolding to make it safe to swap SOON in for JSON in production.
Nine datasets, from trivially flat to adversarially irregular. All numbers are characters vs compact JSON (positive = smaller); generated by benchmarks/run.py.
| Dataset | Compact JSON | YAML | TOON | SOON |
|---|---|---|---|---|
| L1 flat config (6 keys) | 113 | +11.5% | +12.4% | +12.4% |
| L2 flat table (100 rows) | 6,900 | +1.5% | +59.0% | +58.6% |
| L3 primitive arrays | 3,057 | -25.7% | +6.4% | +6.4% |
| L4 nested small (10 orders) | 2,497 | -9.3% | -8.3% | +56.6% |
| L5 nested large (100 orders) | 24,254 | -9.2% | -9.7% | +60.6% |
| L6 deeply nested (depth 5) | 5,660 | -36.3% | -198.6% | +56.2% |
| L7 event log (semi-uniform) | 9,801 | -1.5% | -22.3% | +44.0% |
| L8 mixed-kind fields | 2,950 | -6.5% | -25.2% | +39.5% |
| L9 adversarial (irregular) | 1,412 | -42.1% | +12.3% | +0.1% |
Reading of the table:
- SOON wins every nested dataset (L4–L8) by 39–61%. This is the entire point — real API payloads look like L4–L7.
- TOON is excellent for flat tables (L2) — use it there. SOON is essentially tied.
- On L9 (adversarially irregular data), SOON's never-worse guarantee kicks in and returns compact JSON. TOON has a YAML-style unquoted-literal trick that beats JSON here; we're closing that gap in v0.2.
auto mode compares SOON against compact JSON at encode time and returns whichever is smaller. SOON output is never larger than JSON. You can drop it in blind — the worst case is "you saved nothing on this specific payload."
from soon_format import encode
encode({"tiny": 1}) # returns '{"tiny":1}' — compact JSON, because SOON wasn't smaller
encode(big_nested_doc) # returns SOON — because SOON was smallerpip install soon-formatfrom soon_format import encode, decode, stats
doc = encode(data) # auto mode, never-worse guarantee
data == decode(doc) # True — lossless
stats(data) # {"json_chars": …, "soon_chars": …, "saving": 0.604}
# Real-token cost decisions (requires: pip install "soon-format[tokens]")
# The o200k_base vocab ships inside the wheel — works offline, no network.
encode(data, tokenizer="o200k_base")
stats(data, tokenizer="o200k_base") # adds json_tokens / soon_tokensnpm install @soon-format/soon
# Optional: real-token cost decisions (adds ~2 MB of BPE ranks)
npm install js-tiktokenimport { encode, decode, stats } from "@soon-format/soon";
const doc = encode(data); // same semantics, byte-identical to Python
const original = decode(doc);
// With js-tiktoken installed, encoding and stats agree with the Python side:
encode(data, { tokenizer: "o200k_base" });
stats(data, { tokenizer: "o200k_base" }); // adds jsonTokens / soonTokens# Node
npx @soon-format/cli encode data.json --stats
npx @soon-format/cli decode doc.soon --pretty
# Python
uvx soon-format encode data.json --stats
soon decode doc.soon --pretty
soon check data.json # round-trip verification, exit code 0/1SOON is designed for anywhere you hand nested structured data to an LLM:
- Agent tool outputs. Wrap tool responses before they hit the model. Same information reaches the LLM; 40–60% fewer tokens.
- RAG context. Encode retrieved records (orders, tickets, users, invoices) that go into the prompt.
- Log/event summarization. Compress semi-uniform event streams before asking the model to reason over them.
- Structured extraction outputs. Ask the model to emit SOON when returning tabular results; parse it back with
decode(). - MCP servers / model middleware. A single transform on the way in, another on the way out.
Prompting the model. Add a short header to your system prompt once:
Data is in SOON format.
SHAPE name = {…}declares field names in order. Each row is a tuple(v1,v2,…)matching the shape._means the field is absent.[N]gives the row count.
That's it. Current-generation models (GPT-4o, Claude Sonnet/Opus 4.x, Gemini 2.x) read SOON reliably.
- Flat tabular data with no nesting. CSV is smaller. TOON is great here. SOON is essentially tied — use whichever fits your stack.
- Retrieval-accuracy-critical tasks where positional values may hurt. Positional tuples are denser than key-value pairs; models occasionally miss which slot is which on very long rows. An accuracy benchmark harness is planned for v0.2 — until then, evaluate on your task.
- Tiny payloads.
automode already hands you compact JSON in this case (that's the never-worse guarantee working as intended). No action needed.
- Shape inference. For every array of ≥ 2 uniform objects, the encoder infers a shape (field names + types) and declares it once at the top:
SHAPE customers = {id,name,city}. - Positional tuples. Each row of the array becomes
(v1,v2,v3)— no keys, no quotes on values that don't need them. - Cost gating. For every candidate table, encoder compares the tabular cost against compact JSON. If tabular isn't smaller, it falls back. That's the never-worse guarantee.
Nesting works because the shape mechanism is recursive: nested objects become nested tuples, nested arrays of objects become lists of tuples. Optional fields use ? (in the shape) and _ (in rows). Irregular values escape via !<json>.
The normative spec is 200 lines. The conformance suite has 41 fixtures that every implementation must pass byte-for-byte.
| JSON | YAML | CSV | TOON | SOON | |
|---|---|---|---|---|---|
| Lossless | yes | yes | no | yes | yes |
| Handles nested data | yes | yes | no | degrades | yes |
| Handles flat tables well | ok | ok | yes | yes | ok |
| LLM-readable without training | yes | yes | yes | yes | yes |
| Zero runtime deps | yes | no | yes | yes | yes |
| Never-larger-than-JSON guarantee | — | no | no | no | yes |
| Multi-language byte-identical parity | — | no | no | limited | yes (Py + TS) |
| Nested-payload token savings vs JSON | 0% | negative | n/a | negative | ~60% |
| Path | What |
|---|---|
SPEC.md |
Normative format specification (v0.1) |
conformance/ |
Language-agnostic fixture suite — the contract every implementation must pass |
python/ |
soon-format on PyPI — reference implementation |
ts/packages/soon |
@soon-format/soon on npm |
ts/packages/cli |
@soon-format/cli on npm (npx-runnable) |
benchmarks/ |
Reproducible size + speed benchmarks (nine datasets, three baselines) |
tools/ |
Fixture generator, cross-implementation differential test |
Both implementations pass the same 41 conformance fixtures byte-for-byte, plus property-based round-trip tests (decode(encode(x)) == x, Hypothesis / seeded fuzzing) and a differential CI job that byte-compares encoder outputs across languages.
We're actively working on making SOON the smallest and fastest way to send JSON to any LLM. Highlights:
- v0.2 — Real-token cost model driving encoder decisions, inline single-use shapes, relaxed literals for the fallback path, ELIDE (default-value elision), REF (repeated-subtree dedup), retrieval-accuracy benchmark.
- v0.3 — Rust-backed core (
soon-format[fast]) targetingjson.dumpsparity, adaptive per-subtree encoding + DICT, streaming APIs. - v0.4 — MCP server, LangChain/LlamaIndex document transformers, Vercel AI SDK middleware, VS Code extension, web playground.
See the roadmap tracker or ROADMAP.md for details, evidence, and definition-of-done for each item.
Is SOON just YAML with a schema? No. YAML is a general human-serialization format; SOON is a specialized encoding tuned exclusively to minimize LLM token cost on nested JSON. YAML expands nested data (see L4–L8 benchmarks); SOON contracts it. YAML has known ambiguity in scalar parsing; SOON has a strict, unambiguous scalar grammar (SPEC §5).
How is this different from TOON? TOON pioneered token-oriented encoding and wins on flat tabular data. On nested data (L4–L8) TOON's list form expands beyond compact JSON — sometimes dramatically (L6 = -199%). SOON's shape/tuple mechanism is recursive by design, so nesting saves tokens instead of costing them.
Is the encoding lossless?
Yes. decode(encode(x)) == x on every JSON-representable input, verified in CI by property-based tests and by 41 conformance fixtures. If you find a counter-example, open a bug — we freeze failures into the conformance suite.
What about model accuracy? Size is proven and reproducible. Accuracy on retrieval-style tasks is what a v0.2 harness will measure across current LLMs. Until it lands, treat SOON as size-proven and evaluate accuracy on your task — same discipline you'd apply to any prompt-engineering change.
Which LLMs support SOON? All of them. SOON is text; there's no model-side integration to install. Current-generation models (GPT-4o, Claude Sonnet/Opus 4.x, Gemini 2.x, Llama 3.x) read it reliably given a one-sentence prompt header. Older / smaller models benefit from a fuller instruction.
Do I have to change my producer code? No. Encode at the LLM boundary; the rest of your system keeps producing normal JSON.
Will there be a Rust / Go / … port? Yes — the conformance suite makes new ports verifiable from day one. See issue #7 for the Rust core we're planning to back Python via PyO3.
We'd love your help. See CONTRIBUTING.md.
- Spec first, fixtures are the contract. No mechanism lands in code before it lands in
SPEC.mdandconformance/. - Zero runtime dependencies in core libraries — tokenizer support stays behind optional extras.
- Conventional Commits with scopes
python,ts,spec,conformance.
Good first issues are labelled here. Ports to new languages are especially welcome.
MIT © Yasin Ughur. If SOON saves you tokens, star the repo — it helps others find it.