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 β’ Key folding / Path expansion support β’ 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 3.0 (2025-11-24)
and implements the following features:
- Canonical number formatting (no trailing zeros, no leading zeros except
0;-0normalized to0by default) - Correct escape sequences for strings (
\\,\",\n,\r,\t) - Three delimiter types: comma (default), tab, pipe
- Array length validation
- Object key order preservation
- Array order preservation
- Tabular format for uniform object arrays
- Inline format for primitive arrays
- Expanded list format for nested structures
- Key folding to collapse single-key object chains into dotted paths
- Configurable flatten depth to limit the depth of key folding
- Collision avoidance so folded keys never collide with existing sibling keys
- Configurable encoding limits for security
TOONDecoder conforms to TOON specification version 3.0 (2025-11-24)
and implements the following features:
- Correct escape sequence parsing (
\\,\",\n,\r,\t) - Three delimiter types: comma (default), tab, pipe
- Array length validation
- Tabular format parsing with field headers
- Inline format for primitive arrays
- Expanded list format for nested structures
- Path expansion to unfold dotted keys into nested objects (inverse of key folding)
- 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.3.0")
]Then add the dependency to your target:
.target(name: "YourTarget", dependencies: ["ToonFormat"])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"
)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
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) // "3.0"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 3.0 (2025-11-24) with full encoding and decoding support.
See CONTRIBUTING.md for detailed guidelines.
- π TOON Spec - Official specification
- π Issues - Bug reports and features
- π€ Contributing - Contribution guidelines
MIT License β see LICENSE.md for details