Compact, human-readable serialization format for LLM contexts with 30-60% token reduction vs JSON. Combines YAML-like indentation with CSV-like tabular arrays. Full compatibility with the official TOON specification.
Key Features: Minimal syntax β’ Tabular arrays for uniform data β’ Array length validation β’ Swift 6.0+ β’ Configurable delimiters β’ Nested field groups β’ Keyed tabular form β’ Comments β’ Strict mode β’ Linux compatibility.
LLM tokens are expensive, and JSON is verbose. TOON saves tokens while remaining human-readable by using indentation for structure and a tabular format for uniform data:
JSON:
{
"users": [
{ "id": 1, "name": "Alice", "role": "admin" },
{ "id": 2, "name": "Bob", "role": "user" }
]
}TOON:
users[2]{id,name,role}:
1,Alice,admin
2,Bob,user
For full details on TOON's design, benchmarks, and specification, see the TOON specification.
TOONEncoder conforms to TOON specification version 4.1 (2026-07-26)
and implements the following features:
- Canonical number formatting with enough precision to read back exactly
- Escape sequences for strings (
\\,\",\n,\r,\t, and\uXXXXfor the other control characters) - Three delimiter types: comma (default), tab, pipe
- Object key order preservation
- Array order preservation
- Tabular format for uniform object arrays
- Nested field groups that collapse a uniform nested-object column into the header
- Keyed tabular format for an object whose values are uniform objects
- Inline format for primitive arrays
- Expanded list format for nested structures
- Canonical empty-array form (
key: []) - Configurable encoding limits for security
TOONDecoder conforms to TOON specification version 4.1 (2026-07-26)
and implements the following features:
- Escape sequence parsing (
\\,\",\n,\r,\t,\uXXXX) - Comment lines (
#), removed in a lexical pre-pass - Byte-order mark removal, CRLF input, trailing-space stripping
- The normative number grammar, so
+5,.5,0x10andNaNstay strings - Three delimiter types: comma (default), tab, pipe
- Tabular format parsing with field headers and nested field groups
- Keyed tabular format parsing
- Inline format for primitive arrays
- Expanded list format for nested structures
- Strict mode (default) with the full error surface of section 14
- Key identity by Unicode scalar sequence, so two keys that differ only in normalization form stay apart
- Detailed error reporting with line numbers
- Configurable decoding limits for security
- Swift 6.0+ / Xcode 16+
- iOS 13.0+ / macOS 10.15+ / watchOS 6.0+ / tvOS 13.0+ / visionOS 1.0+ / Linux
Add the following to your Package.swift file:
dependencies: [
.package(url: "https://github.com/toon-format/toon-swift.git", from: "0.5.0")
]Then add the dependency to your target:
.target(name: "YourTarget", dependencies: ["ToonFormat"])Release 0.5.0 moves from TOON specification 3.0 to 4.1. The format changed between those versions, so the output changes too.
| Change | What to do |
|---|---|
encoder.indent is renamed encoder.indentSize |
Rename the property. The old name still works and warns. |
| The decoder no longer guesses the indentation size | Set decoder.indentSize when a document does not use two spaces. |
decoder.strict is new and defaults to true |
Set decoder.strict = false to accept a document that specification 14 rejects. |
keyFolding and flattenDepth are deprecated |
Specification 4.0 removed key folding. They stay off by default and go away in 2.0. |
expandPaths is deprecated and now defaults to .disabled |
A dotted key is one literal key. To read a document written with key folding, set .safe and re-encode. |
An empty array is written key: [], not key[0]: |
Nothing; both forms decode. |
| A dictionary of uniform objects is written in the keyed tabular form | Nothing; the form round-trips. |
indentSize below 1 throws |
Pass 1 or more. The decoder used to flatten the document, and the encoder used to stop the process. |
A number that a Float or a UInt64 cannot hold throws |
Decode into Double or String instead. The value used to become an infinity, or to lose its sign check. |
| The line number of an error moved | A test that asserts on a line number needs new values. The number now names the line that carries the defect. |
| Outside strict mode the decoder accepts more | A row of the wrong width and a line after the root value no longer throw, which follows specification 14. |
import ToonFormat
struct User: Codable {
let id: Int
let name: String
let tags: [String]
let active: Bool
}
// Encoding
let user = User(
id: 123,
name: "Ada",
tags: ["reading", "gaming"],
active: true
)
let encoder = TOONEncoder()
let data = try encoder.encode(user)
print(String(data: data, encoding: .utf8)!)
// id: 123
// name: Ada
// tags[2]: reading,gaming
// active: true
// Decoding
let decoder = TOONDecoder()
let decoded = try decoder.decode(User.self, from: data)
print(decoded.name) // "Ada"TOONEncoder preserves the field order defined by Encodable types.
When encoding Swift Dictionary values, keys are sorted lexicographically.
This helps ensure deterministic output while preserving semantics of encoded data structures.
Note
Dictionary key ordering is best-effort and relies on a heuristic that may change with Swift internals.
struct ShoppingList: Codable {
let name: String
let itemsAndCounts: [String: Int]
}
let list = ShoppingList(
name: "Groceries",
itemsAndCounts: ["cherries": 3, "apple": 1, "banana": 2]
)
let encoder = TOONEncoder()
let data = try encoder.encode(list)
print(String(data: data, encoding: .utf8)!)
// name: Groceries /* name comes first because it is declared first in the struct */
// itemsAndCounts:
// apple: 1 /* keys in dictionary are sorted */
// banana: 2
// cherries: 3Use tab or pipe delimiters for additional token savings:
struct Item: Codable {
let sku: String
let name: String
let qty: Int
let price: Double
}
let items = [
Item(sku: "A1", name: "Widget", qty: 2, price: 9.99),
Item(sku: "B2", name: "Gadget", qty: 1, price: 14.5)
]
let encoder = TOONEncoder()
encoder.delimiter = .tab // or .pipe
let data = try encoder.encode(["items": items])Output with tab delimiter:
items[2 ]{sku name qty price}:
A1 Widget 2 9.99
B2 Gadget 1 14.5
Output with pipe delimiter:
items[2|]{sku|name|qty|price}:
A1|Widget|2|9.99
B2|Gadget|1|14.5
TOONEncoder defaults to canonical normalization that matches the spec and the
reference JavaScript implementation. You can override this behavior when you
need to preserve negative zero or handle non-finite values explicitly.
let encoder = TOONEncoder()
encoder.negativeZeroEncodingStrategy = .preserve
encoder.nonConformingFloatEncodingStrategy = .convertToString(
positiveInfinity: "Infinity",
negativeInfinity: "-Infinity",
nan: "NaN"
)An integer that does not fit Int64 and a decimal that overflows Double,
such as 1e400, decode as strings, so no digit is lost.
A UInt64 above Int64.max encodes as a quoted decimal string and decodes back from it.
The encoder writes these Swift types as follows, and the decoder reads them back:
| Swift type | TOON value |
|---|---|
Date |
ISO 8601 string with fractional seconds |
URL |
absoluteString |
Data |
Base64 string |
nil |
null |
Arrays of objects with identical primitive fields use an efficient tabular format:
struct Item: Codable {
let sku: String
let qty: Int
let price: Double
}
let items = [
Item(sku: "A1", qty: 2, price: 9.99),
Item(sku: "B2", qty: 1, price: 14.5)
]
let encoder = TOONEncoder()
let data = try encoder.encode(["items": items])Output:
items[2]{sku,qty,price}:
A1,2,9.99
B2,1,14.5
For arrays containing primitive inner arrays:
let pairs = [[1, 2], [3, 4]]
let encoder = TOONEncoder()
let data = try encoder.encode(["pairs": pairs])Output:
pairs[2]:
- [2]: 1,2
- [2]: 3,4
Specification 9.3 collapses a uniform nested-object column into the header, while the rows stay flat:
orders[2]{id,customer{name,country},total}:
1,Ada,DK,99
2,Bob,UK,149
Specification 9.5 gives an object whose values are uniform objects a tabular
form of its own. A Swift dictionary such as [String: Server] encodes this
way:
servers[2:]{host,port}:
alpha: a.example.com,8080
beta: b.example.com,9090
A decoder removes a comment line before anything else reads the document. A
comment is a full line whose first character after zero or more spaces is #;
there is no inline form. An encoder never writes one.
# the rows below are unaffected by this line
users[2]{id,name}:
1,Ada
2,Bob
Warning
TOON specification 4.0 removed key folding, and a decoder now reads a dotted key as one literal key. The option still works and stays off by default, so the encoder conforms unless you opt out. It is removed in 2.0.
To read a document that an earlier release wrote with key folding, decode it
with decoder.expandPaths = .safe, then encode it again.
Key folding collapses single-key nested objects into dotted paths, reducing indentation and token count:
struct Config: Codable {
struct Database: Codable {
struct Connection: Codable {
let host: String
let port: Int
}
let connection: Connection
}
let database: Database
}
let config = Config(
database: .init(
connection: .init(host: "localhost", port: 5432)
)
)
let encoder = TOONEncoder()
encoder.keyFolding = .safe
let data = try encoder.encode(config)Without key folding:
database:
connection:
host: localhost
port: 5432
Output with key folding (encoder.keyFolding = .safe):
database.connection:
host: localhost
port: 5432
Protect against stack overflow from deeply nested structures:
let encoder = TOONEncoder()
encoder.limits = TOONEncoder.EncodingLimits(maxDepth: 64)| Limit | Default | Description |
|---|---|---|
maxDepth |
32 | Maximum nesting depth |
Use .unlimited for trusted data only.
Protect against malicious or malformed input:
let decoder = TOONDecoder()
decoder.limits = TOONDecoder.DecodingLimits(
maxInputSize: 1024 * 1024, // 1 MB
maxDepth: 64,
maxObjectKeys: 1000,
maxArrayLength: 10000
)| Limit | Default | Description |
|---|---|---|
maxInputSize |
10 MB | Maximum input size in bytes |
maxDepth |
32 | Maximum nesting depth |
maxObjectKeys |
10,000 | Maximum keys per object |
maxArrayLength |
100,000 | Maximum elements per array |
Use .unlimited for trusted data only.
Check the supported TOON specification version:
print(toonSpecVersion) // "4.1"Contributions are welcome! Please read our Contributing Guide for details on how to get started, coding standards, and the process for submitting pull requests.
Before contributing, please review:
This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to hello@johannschopplich.com.
This library implements TOON specification version 4.1 (2026-07-26) with full encoding and decoding support. It satisfies all 538 conformance fixtures published by the specification.
See CONTRIBUTING.md for detailed guidelines.
- π TOON Spec - Official specification
- π Issues - Bug reports and features
- π€ Contributing - Contribution guidelines
MIT License β see LICENSE for details