Large language models are great at text, but they are surprisingly picky eaters when it comes to structure. Plain JSON is easy for humans and machines, but it is not always token efficient for LLMs. Quotes, long keys, and noisy punctuation inflate token count and can blur attention for models that were not trained to treat JSON punctuation as special. In production LLM systems where prompts are large or frequent, a leaner representation can save tokens, reduce latency, and improve parsing robustness.
Enter TOON: Token-Oriented Object Notation. Think of it as JSON that has hit the gym and learned to pack lightly. TOON keeps the spirit of structured key-value data but trims redundancy by using short schema-defined tokens, concise separators, and streaming-friendly framing. It aims to be easy to implement in a day, safe to validate, and clear to read once you know the dictionary.
If you are building retrieval, grounding, or agent loops, pair this with RAG for SaaS and Agentic Workflows for Developer Automation. To integrate with Next.js, see Integrate OpenAI API in Next.js and Document Q and A with Next.js + LangChain.
Why plain JSON is not always ideal for LLMs
- Long, repeated keys waste tokens that could hold more examples or instructions.
- Quoted strings and verbose punctuation can create brittle parsing under temperature.
- Nested structures increase prompt size quickly, especially in few-shot and tool-using prompts.
You can minify JSON and shorten keys, but team-wide readability suffers, and without a clear dictionary you risk mismatches. TOON formalizes the short-key idea with a lightweight envelope, predictable delimiters, and validation rules. The goal is not to replace JSON everywhere. The goal is to have a compact, LLM-friendly format for the parts of your system where token budget is tight.
TOON at a glance
TOON uses a small dictionary that maps short tokens to semantic keys. The payload references only tokens. Objects and arrays use compact delimiters and minimal quoting rules to reduce token count while staying unambiguous.
Example
Human JSON:
{
"user": {"name": "Alice", "email": "a@example.com"},
"items": [{"id": "SKU123", "qty": 2}]
}TOON dictionary:
{
"u": "user",
"n": "name",
"e": "email",
"i": "items",
"id": "id",
"q": "qty"
}TOON payload:
u(n=Alice,e=a@example.com),i([id=SKU123|q=2])Rules:
- Objects use parentheses:
(k=v,k=v,...) - Arrays use brackets with vertical bars:
[item|item|...] - Key and value are separated by
= - Strings are unquoted if they contain only safe characters
[A-Za-z0-9@._-]; otherwise wrap in single quotes and escape'as'' - Whitespace is optional and ignored
This is compact, readable enough with a dictionary, and parsable with a small state machine. It also plays well with tokenizers that treat simple ASCII punctuation as cheap tokens.
Architecture sketch
Producer Transport Consumer
│ │ │
▼ ▼ ▼
JSON object ──► TOON encode ──► to_string() ──► send ──► read ──► parse TOON ──► JSON object
│ ▲ \n-framed chunks │ ▲
└────────────┴────────────────────────────────┴───────────────────────────────┘
Notes:
- Dictionary id travels with the payload header.
- Streaming uses newline framed chunks for incremental decode.For system design patterns around clean interfaces, see Clean Architecture for Fullstack and for production deployment considerations, see Next.js SEO Best Practices for pairing fast content with good crawl signals.
TypeScript reference implementation
The implementation below shows a pragmatic TOON encoder and decoder with a small set of rules that are easy to audit. It trades full JSON expressiveness for token efficiency and predictable parsing.
// utils/toon.ts
export type ToonDict = Record<string, string>; // token -> semantic key
export type ReverseDict = Record<string, string>; // semantic key -> token
export type ToonEnvelope = {
dictId: string; // versioned dictionary id
dict: ToonDict; // optional in-band dict for demo or ad-hoc flows
payload: string; // TOON text
};
const SAFE_CHARS = /^[A-Za-z0-9@._-]+$/;
const escapeValue = (value: string): string => {
if (value.length === 0) return \"''\";
if (SAFE_CHARS.test(value)) return value;
// Use single quotes and escape single quotes by doubling
return `'${value.replace(/'/g, \"''\")}'`;
};
const unescapeValue = (src: string): string => {
if (src.startsWith(\"'\") && src.endsWith(\"'\")) {
const inner = src.slice(1, -1);
return inner.replace(/''/g, \"'\");
}
return src;
};
export const makeReverseDict = (dict: ToonDict): ReverseDict => {
const rev: ReverseDict = {};
for (const [t, k] of Object.entries(dict)) rev[k] = t;
return rev;
};
export const encodeToon = (data: unknown, dict: ToonDict): string => {
const rev = makeReverseDict(dict);
const encode = (node: any): string => {
if (node === null) return \"null\"; // literal
if (typeof node === \"boolean\") return node ? \"true\" : \"false\";
if (typeof node === \"number\") return Number.isFinite(node) ? String(node) : \"null\";
if (typeof node === \"string\") return escapeValue(node);
if (Array.isArray(node)) {
return `[${node.map(encode).join(\"|\")}]`;
}
if (typeof node === \"object\") {
const parts: string[] = [];
for (const [key, value] of Object.entries(node)) {
const token = rev[key];
if (!token) throw new Error(`Missing token for key: ${key}`);
parts.push(`${token}=${encode(value)}`);
}
return `(${parts.join(\",\")})`;
}
return \"null\";
};
return encode(data);
};
// A minimal parser for the subset described above
export const decodeToon = (src: string, dict: ToonDict): any => {
let i = 0;
const peek = () => src[i];
const next = () => src[i++];
const eat = (ch: string) => {
if (next() !== ch) throw new Error(`Expected '${ch}' at ${i - 1}`);
};
const skipWs = () => {
while (src[i] === ' ' || src[i] === '\\n' || src[i] === '\\t' || src[i] === '\\r') i++;
};
const readToken = (): string => {
let start = i;
while (/[A-Za-z0-9_-]/.test(peek())) i++;
if (start === i) throw new Error(`Expected token at ${i}`);
return src.slice(start, i);
};
const readValue = (): any => {
skipWs();
const ch = peek();
if (ch === '(') {
next();
const obj: Record<string, any> = {};
skipWs();
if (peek() === ')') {
next();
return obj;
}
while (true) {
skipWs();
const token = readToken();
skipWs();
eat('=');
const val = readValue();
const key = dict[token];
if (!key) throw new Error(`Unknown token: ${token}`);
obj[key] = val;
skipWs();
const sep = next();
if (sep === ')') break;
if (sep !== ',') throw new Error(`Expected ',' or ')' at ${i - 1}`);
}
return obj;
}
if (ch === '[') {
next();
const arr: any[] = [];
skipWs();
if (peek() === ']') {
next();
return arr;
}
while (true) {
const val = readValue();
arr.push(val);
skipWs();
const sep = next();
if (sep === ']') break;
if (sep !== '|') throw new Error(`Expected '|' or ']' at ${i - 1}`);
}
return arr;
}
if (ch === \"'\") {
next();
let out = '';
while (i < src.length) {
const c = next();
if (c === \"'\") {
if (peek() === \"'\") {
next(); // escaped quote
out += \"'\";
continue;
}
break;
}
out += c;
}
return out;
}
// literal or unquoted string
const start = i;
while (i < src.length && /[^,)|\\s]/.test(peek())) i++;
const raw = src.slice(start, i);
if (raw === \"null\") return null;
if (raw === \"true\") return true;
if (raw === \"false\") return false;
if (/^-?\\d+(?:\\.\\d+)?$/.test(raw)) return Number(raw);
return unescapeValue(raw);
};
const value = readValue();
skipWs();
if (i !== src.length) throw new Error(`Unexpected trailing input at ${i}`);
return value;
};Unit test sketch
import { encodeToon, decodeToon, type ToonDict } from \"./utils/toon\";
const dict: ToonDict = { u: \"user\", n: \"name\", e: \"email\", i: \"items\", id: \"id\", q: \"qty\" };
const json = { user: { name: \"Alice\", email: \"a@example.com\" }, items: [{ id: \"SKU123\", qty: 2 }] };
const toon = encodeToon(json, dict);
const decoded = decodeToon(toon, dict);
console.assert(JSON.stringify(decoded) === JSON.stringify(json));Prompting with TOON
LLMs do not need to be JSON parsers to read TOON. Give the model a tiny grammar and a dictionary header.
You are given a TOON dictionary and a TOON payload.
Dictionary maps short tokens to keys.
Grammar:
- Object: (k=v,k=v)
- Array: [v|v]
- Strings: unquoted if alnum and simple punctuation; otherwise 'quote with doubled single quotes'.
Return your answer in TOON with the same dictionary.
Dictionary:
u=user,n=name,e=email,i=items,id=id,q=qtyThis reduces prompt size compared to a full JSON schema and keeps the output predictable. For more complex interactions in tool calls or agent loops, see Agentic Workflows for Developer Automation.
Streaming TOON for long responses
TOON is friendly to line-framed streaming. Produce one complete structure per line or stream array items steadily.
header: dictId=orders_v3
dict: u=user,n=name,e=email,i=items,id=id,q=qty
---
(u(n=Alice,e=a@example.com),i([id=SKU123|q=2]))
(u(n=Bob,e=b@example.com),i([id=SKU789|q=1]))This style works well for incremental rendering and keeps parsing constant-time per line. For front-end rendering and performance across routes, pair with Next.js Core Web Vitals in 2025.
Validating TOON in production
Even compact formats need validation. Use a typed envelope, dictionary versioning, and strict decoders that fail closed.
type Order = { user: { name: string; email: string }; items: Array<{ id: string; qty: number }> };
export const validateOrder = (obj: any): obj is Order => {
if (!obj || typeof obj !== \"object\") return false;
if (!obj.user || typeof obj.user !== \"object\") return false;
if (typeof obj.user.name !== \"string\" || typeof obj.user.email !== \"string\") return false;
if (!Array.isArray(obj.items)) return false;
for (const it of obj.items) {
if (typeof it !== \"object\" || typeof it.id !== \"string\" || typeof it.qty !== \"number\") return false;
}
return true;
};If you rely on evaluations or repo-grounded reasoning, add a small golden dataset that checks encoder, decoder, and validator. For evaluation harness patterns, see AI Coding Assistants: Benefits, Risks, and Adoption.
Safety and security notes
- TOON does not magically make inputs safe. Sanitize, validate, and set size limits.
- Avoid in-band executable instructions. Treat TOON as data, not a program.
- Enforce dictionary versioning. Changes to tokens should be backward compatible or explicitly migrated.
Performance notes and token budgets
Token savings vary by tokenizer and data shape. TOON reduces repeated key names and quotation overhead, which helps on nested documents and event streams. The main wins are:
- Smaller prompts and responses, allowing more examples or context.
- Lower latency and cost for chat completions that return structured data.
- Easier streaming and partial results with line framing.
You should still measure with your model and dataset. Compare P50 and P95 token counts and response times between plain JSON and TOON for your real tasks. For storage and indexing, you can keep JSON at rest and only encode to TOON at the LLM boundary.
Versioning and governance
- Assign a
dictIdand publish a changelog when adding or changing tokens. - Stabilize high-use structures and keep token names short and memorable.
- Add CI checks that the encoder and decoder round-trip across dictionaries.
Tradeoffs
- Humans need the dictionary to read payloads. Mitigate with good naming and tooling.
- Expressiveness is intentionally limited to keep parsing simple.
- Vendor SDKs and tools expect JSON. Keep adapters at the edges and convert only when it helps.
FAQs
Is TOON a replacement for JSON?
No. It is a compact representation for LLM boundaries where token budget and stability matter. Keep JSON for storage, APIs, and general interop.
Can I nest deeply?
Yes, but measure whether deep nesting is helpful. Complex structures often benefit from splitting into multiple TOON lines or separate turns.
What about numbers and dates?
Numbers are unquoted decimal or integer literals. For dates, prefer ISO 8601 strings. You can standardize tokens like ts for timestamp and document their types.
Conclusion
TOON gives you a practical middle ground between verbose JSON and opaque binary formats. It trims token usage, enables clean streaming, and stays simple enough to implement and audit in a day. Use it at the prompt boundary where you need lean structure and predictable parsing, and keep JSON everywhere else.
For next steps, integrate TOON into your agent loop architecture from Agentic Workflows for Developer Automation, pair it with retrieval patterns in RAG for SaaS, and wire it to your Next.js app using the patterns in Integrate OpenAI API in Next.js.
Actionable takeaways
- Start small. Encode one response type with TOON and measure token savings and latency on real prompts.\n\n2) Version your dictionary. Assign a
dictId, publish changes, and add round-trip tests.\n\n3) Stream by line. Use TOON lines for incremental results. Keep decoders strict and fail closed.
