toon-format/toon-swift
Compact, human-readable serialization format for LLM contexts with **30-60% token reduction** vs JSON.
Features
TOONEncoder
TOONEncoder conforms to TOON specification version 3.0 (2025-11-24) and implements the following features:
- [x] Canonical number formatting (no trailing zeros, no leading zeros except
0;-0normalized to0by default) - [x] Correct escape sequences for strings (
\\,\",\n,\r,\t) - [x] Three delimiter types: comma (default), tab, pipe
- [x] Array length validation
- [x] Object key order preservation
- [x] Array order preservation
- [x] Tabular format for uniform object arrays
- [x] Inline format for primitive arrays
- [x] Expanded list format for nested structures
- [x] Key folding to collapse single-key object chains into dotted paths
- [x] Configurable flatten depth to limit the depth of key folding
- [x] Collision avoidance so folded keys never collide with existing sibling keys
- [x] Configurable encoding limits for security
TOONDecoder
TOONDecoder conforms to TOON specification version 3.0 (2025-11-24) and implements the following features:
- [x] Correct escape sequence parsing (
\\,\",\n,\r,\t) - [x] Three delimiter types: comma (default), tab, pipe
- [x] Array length validation
- [x] Tabular format parsing with field headers
- [x] Inline format for primitive arrays
- [x] Expanded list format for nested structures
- [x] Path expansion to unfold dotted keys into nested objects (inverse of key folding)
- [x] Detailed error reporting with line numbers
- [x] Configurable decoding limits for security
Requirements
- Swift 6.0+ / Xcode 16+
- iOS 13.0+ / macOS 10.15+ / watchOS 6.0+ / tvOS 13.0+ / visionOS 1.0+ / Linux
Installation
Swift Package Manager
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"])Usage
Quick Start
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"Object Key Ordering
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: 3Custom Delimiters
Use 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.5Output with pipe delimiter:
items[2|]{sku|name|qty|price}:
A1|Widget|2|9.99
B2|Gadget|1|14.5Numeric Normalization
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"
)Tabular Arrays
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.5Arrays of Arrays
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,4Key Folding
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: 5432Output with key folding (encoder.keyFolding = .safe):
database.connection:
host: localhost
port: 5432Encoding Limits
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.
Decoding Limits
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.
Version Information
Check the supported TOON specification version:
print(toonSpecVersion) // "3.0"Contributing
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:
Code of Conduct
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.
Project Status
This library implements TOON specification version 3.0 (2025-11-24) with full encoding and decoding support.
See CONTRIBUTING.md for detailed guidelines.
Documentation
- π TOON Spec - Official specification
- π Issues - Bug reports and features
- π€ Contributing - Contribution guidelines
License
MIT License β see LICENSE.md for details
Package Metadata
Repository: toon-format/toon-swift
Default branch: main
README: README.md