TOON Format Explained: Where It Saves Tokens and Where It Does Not
TOON (Token-Oriented Object Notation) is a text format for the same data model as JSON: objects, arrays, strings, numbers, booleans and null. It exists for one purpose, which is putting structured data into an LLM prompt with fewer tokens. It is not a replacement for JSON in APIs or storage. Whether it helps depends almost entirely on the shape of your data, so this article shows both the good case and the bad one.
The syntax in five rules
1. Objects use indentation instead of braces, and keys and most string values are unquoted.
{
"player": {
"name": "Shadow Knight",
"level": 42,
"active": true
}
}player: name: Shadow Knight level: 42 active: true
2. Arrays of primitives go on one line, with the length declared in brackets.
tags[3]: admin,ops,dev3. Arrays of uniform objects become a table. The header states the length and the field names once; each row then lists only values. This is where nearly all of the savings come from.
users[3]{id,name,role}: 1,Alice,admin 2,Bob,user 3,Cara,user
4. Arrays that are not uniform fall back to a list, one - item per element, similar to YAML.
items[3]: - id: 1 name: A - id: 2 tags[1]: x - plain string
5. Strings are quoted only when they would be misread. The encoder adds quotes when a value could be taken for another type or would break the line structure:
| Value | Encoded as | Reason |
|---|---|---|
| hello world | hello world | Unambiguous, inner spaces are fine |
| the string "true" | "true" | Would decode as a boolean |
| the string "42" or "007" | "42", "007" | Would decode as a number |
| a,b | "a,b" | Contains the delimiter |
| x: y | "x: y" | Contains a colon |
| empty string | "" | Otherwise invisible |
| a value with leading or trailing spaces | " pad " | Whitespace would be trimmed |
Measured: flat data versus nested data
We encoded two datasets with the reference TypeScript library (@toon-format/toon 2.1) and counted characters. Characters are not tokens, but for ASCII-heavy data they move together, and character counts can be reproduced without choosing a tokenizer.
The second result surprises people. Adding one nested object to each record makes the array ineligible for the table form, so the encoder falls back to the list form and repeats every key on its own indented line. The output is still smaller than pretty-printed JSON, but larger than minified JSON.
The TOON project's own token benchmarks (GPT o200k_base tokenizer) show the same pattern:
| Dataset | TOON vs. formatted JSON | TOON vs. minified JSON |
|---|---|---|
| Uniform employee records (100% tabular) | −60.7% | −36.8% |
| E-commerce orders, nested (33% tabular) | −33.1% | +5.5% |
| Semi-uniform event logs (50% tabular) | −15.0% | +19.9% |
| Deeply nested configuration (0% tabular) | −31.3% | +11.9% |
Source: the benchmarks section of the TOON repository. Negative numbers mean TOON used fewer tokens.
A rule of thumb
- Use TOON when the bulk of the payload is arrays of objects that share the same primitive fields: database rows, search results, product lists, time series, log records with a fixed schema.
- Use minified JSON for deeply nested or irregular structures such as configuration trees, or when the arrays hold objects with differing keys.
- Use CSV if the data is one flat table and nothing else. CSV is slightly smaller still; TOON spends a few percent on the length and field declarations.
- Flatten first if you control the shape. Turning
address.cityinto a top-levelcitycolumn before encoding restores the table form and the savings with it.
Using TOON in prompts
- Show the format, do not explain it. Models read TOON without instructions when the data is placed in a fenced code block labelled
toon. - The header is a guardrail.
[3]tells the model how many rows to expect and{id,name,role}names the columns. That structure helps with questions like "how many users are admins" and makes truncated input detectable. - For output, give the header. When you want the model to answer in TOON, show the header line you expect and let it fill in rows. Then decode with strict validation; see decoding TOON back to JSON.
- Measure latency too. Fewer tokens usually means faster responses, but some local model setups handle compact JSON faster. Test on your own stack.
Encoding in code
import { encode } from '@toon-format/toon';
const prompt = [
'Answer using the data below.',
'```toon',
encode({ users }),
'```',
].join('\n');The conversion is lossless: decoding the result gives back the same values. Implementations exist for Python, Go, Rust, Java and other languages, all following one written specification.