A Clojure/ClojureScript library for TOON - a data format that uses ~50% fewer tokens than JSON when talking to LLMs.
(require '[com.vadelabs.toon.core :as toon])
(toon/encode {:name "Alice" :age 30 :tags ["dev" "clj"]})
;=> "name: Alice\nage: 30\ntags[2]: dev,clj"
(toon/decode "name: Alice\nage: 30\ntags[2]: dev,clj")
;=> {"name" "Alice", "age" 30.0, "tags" ["dev" "clj"]}LLM tokens cost money. JSON is verbose. TOON fixes that.
The format borrows YAML's indentation for nesting and CSV's row-based structure for tabular data. The sweet spot is arrays of uniform objects - declare the keys once, then stream values as rows:
users[3]{id,name,role}:
1,Alice,admin
2,Bob,user
3,Carol,user
vs JSON:
{"users":[{"id":1,"name":"Alice","role":"admin"},{"id":2,"name":"Bob","role":"user"},{"id":3,"name":"Carol","role":"user"}]}Benchmarks using GPT's o200k_base tokenizer show 49% fewer tokens than formatted JSON, 28% fewer than minified JSON.
;; deps.edn
com.vadelabs/toon {:mvn/version "2026.01.05"}
;; Leiningen
[com.vadelabs/toon "2026.01.05"]Works on Clojure, ClojureScript, and Babashka.
Objects - key-value pairs, indentation for nesting:
name: Alice
address:
city: Portland
zip: 97201
Inline arrays - primitives on one line:
tags[3]: reading,gaming,coding
Tabular arrays - uniform objects, keys declared once:
users[2]{id,name}:
1,Alice
2,Bob
List arrays - mixed or nested items:
items[2]:
- name: Laptop
price: 999
- name: Mouse
price: 29
Strings get quoted when they contain special characters (commas, colons, etc).
(toon/encode data)
(toon/encode data {:indent 2
:delimiter "," ; or "\t" or "|"
:key-collapsing :off ; or :safe
:replacer (fn [k v path] v)})The :replacer option works like JSON.stringify's replacer - return nil to omit a field:
(toon/encode {:name "Alice" :password "secret"}
{:replacer (fn [k v _] (when-not (= k "password") v))})
;=> "name: Alice"(toon/decode toon-string)
(toon/decode toon-string {:indent 2
:strict true})For large documents, use event-based decoding:
(toon/events toon-string)
;=> ({:type :start-object} {:type :key :key "name"} ...)
(toon/events->value (toon/events toon-string))
;=> {"name" "Alice" ...}Clojure types are normalized to JSON-compatible values:
| Clojure | TOON/JSON |
|---|---|
:keyword |
"keyword" |
#{1 2 3} |
[1, 2, 3] (sorted) |
42 |
42.0 |
{:a 1} |
{"a": 1} |
TOON shines with uniform, tabular data - think database rows, API responses with repeated structure, analytics data.
For deeply nested or irregular data, the savings are smaller. For flat CSVs with no nesting, plain CSV is more compact.
bb test # Run all tests (clj + bb + cljs)
bb test:clj # Clojure only
bb coverage # Generate coverage report540+ tests, 90%+ coverage.
This implements TOON v3.0. The reference implementation is in TypeScript.
Community implementations exist for Go, Python, Ruby, Swift, PHP, and others - see the spec repo for a full list.
MIT. Copyright 2025 Vade Labs.