toon-format 0.3.1

Token-Oriented Object Notation (TOON) - a token-efficient JSON alternative for LLM prompts
Documentation

TOON Format for Rust

Crates.io Documentation Spec v2.0 License: MIT Tests

Token-Oriented Object Notation (TOON) is a compact, human-readable format designed for passing structured data to Large Language Models with significantly reduced token usage.

This crate provides the official, spec-compliant Rust implementation of TOON v2.0 with v1.5 optional features, offering both a library (toon-format) and a full-featured command-line tool (toon).

Quick Example

JSON (16 tokens, 40 bytes):

{
  "users": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ]
}

TOON (13 tokens, 28 bytes) - 18.75% token savings:

users[2]{id,name}:
  1,Alice
  2,Bob

Features

  • Spec-Compliant: Fully compliant with TOON Specification v2.0
  • v1.5 Optional Features: Key folding and path expansion
  • Safe & Performant: Built with safe, fast Rust
  • Serde Integration: Native support for serde_json::Value
  • Powerful CLI: Full-featured command-line tool
  • Strict Validation: Enforces all spec rules (configurable)
  • 100% Test Coverage: 109 unit tests + 22 spec fixture tests passing

Installation

As a Library

cargo add toon-format

As a CLI Tool

cargo install toon-format

Library Usage

Basic Encode & Decode

use serde_json::json;
use toon_format::{encode_default, decode_default};

fn main() -> Result<(), toon_format::ToonError> {
    let data = json!({
        "users": [
            {"id": 1, "name": "Alice"},
            {"id": 2, "name": "Bob"}
        ]
    });

    // Encode to TOON
    let toon_str = encode_default(&data)?;
    println!("{}", toon_str);
    // Output:
    // users[2]{id,name}:
    //   1,Alice
    //   2,Bob

    // Decode back to JSON
    let decoded = decode_default(&toon_str)?;
    assert_eq!(decoded, data);
    
    Ok(())
}

API Reference

Encoding

encode(&value, &options) -> Result<String, ToonError>

Encode a serde_json::Value to TOON format.

use toon_format::{encode, EncodeOptions, Delimiter, Indent};
use serde_json::json;

let data = json!({"items": ["a", "b", "c"]});

// Default encoding
let toon = encode(&data, &EncodeOptions::default())?;
// items[3]: a,b,c

// Custom delimiter
let opts = EncodeOptions::new()
    .with_delimiter(Delimiter::Pipe);
let toon = encode(&data, &opts)?;
// items[3|]: a|b|c

// Custom indentation
let opts = EncodeOptions::new()
    .with_indent(Indent::Spaces(4));
let toon = encode(&data, &opts)?;

EncodeOptions

Method Description Default
with_delimiter(d) Set delimiter: Comma, Tab, or Pipe Comma
with_indent(i) Set indentation (spaces only) Spaces(2)
with_spaces(n) Shorthand for Indent::Spaces(n) 2
with_key_folding(mode) Enable key folding (v1.5) Off
with_flatten_depth(n) Set max folding depth usize::MAX

Decoding

decode(&input, &options) -> Result<Value, ToonError>

Decode TOON format to serde_json::Value.

use toon_format::{decode, DecodeOptions};

let toon = "name: Alice\nage: 30";

// Default (strict) decode
let json = decode(toon, &DecodeOptions::default())?;

// Non-strict mode (relaxed validation)
let opts = DecodeOptions::new()
    .with_strict(false);
let json = decode(toon, &opts)?;

// Disable type coercion
let opts = DecodeOptions::new()
    .with_coerce_types(false);
let json = decode("active: true", &opts)?;
// With coercion: {"active": true}
// Without: {"active": "true"}

DecodeOptions

Method Description Default
with_strict(b) Enable strict validation true
with_coerce_types(b) Auto-convert strings to types true
with_expand_paths(mode) Enable path expansion (v1.5) Off

v1.5 Features

Key Folding (Encoder)

New in v1.5: Collapse single-key object chains into dotted paths to reduce tokens.

Standard nesting:

data:
  metadata:
    items[2]: a,b

With key folding:

data.metadata.items[2]: a,b

Example

use toon_format::{encode, EncodeOptions, KeyFoldingMode};
use serde_json::json;

let data = json!({
    "data": {
        "metadata": {
            "items": ["a", "b"]
        }
    }
});

// Enable key folding
let opts = EncodeOptions::new()
    .with_key_folding(KeyFoldingMode::Safe);

let toon = encode(&data, &opts)?;
// Output: data.metadata.items[2]: a,b

With Depth Control

let data = json!({"a": {"b": {"c": {"d": 1}}}});

// Fold only 2 levels
let opts = EncodeOptions::new()
    .with_key_folding(KeyFoldingMode::Safe)
    .with_flatten_depth(2);

let toon = encode(&data, &opts)?;
// Output:
// a.b:
//   c:
//     d: 1

Safety Features

Key folding only applies when:

  • All segments are valid identifiers (a-z, A-Z, 0-9, _)
  • Each level contains exactly one key
  • No collision with sibling literal keys
  • Within the specified flatten_depth

Keys like full-name, user.email (if quoted), or numeric keys won't be folded.

Path Expansion (Decoder)

New in v1.5: Automatically expand dotted keys into nested objects.

Compact input:

a.b.c: 1
a.b.d: 2
a.e: 3

Expanded output:

{
  "a": {
    "b": {
      "c": 1,
      "d": 2
    },
    "e": 3
  }
}

Example

use toon_format::{decode, DecodeOptions, PathExpansionMode};

let toon = "a.b.c: 1\na.b.d: 2";

// Enable path expansion
let opts = DecodeOptions::new()
    .with_expand_paths(PathExpansionMode::Safe);

let json = decode(toon, &opts)?;
// {"a": {"b": {"c": 1, "d": 2}}}

Round-Trip Example

use toon_format::{encode, decode, EncodeOptions, DecodeOptions, KeyFoldingMode, PathExpansionMode};
use serde_json::json;

let original = json!({
    "user": {
        "profile": {
            "name": "Alice"
        }
    }
});

// Encode with folding
let encode_opts = EncodeOptions::new()
    .with_key_folding(KeyFoldingMode::Safe);
let toon = encode(&original, &encode_opts)?;
// → "user.profile.name: Alice"

// Decode with expansion
let decode_opts = DecodeOptions::new()
    .with_expand_paths(PathExpansionMode::Safe);
let restored = decode(&toon, &decode_opts)?;

assert_eq!(restored, original); // Perfect round-trip!

Quoted Keys Remain Literal

let toon = r#"a.b: 1
"c.d": 2"#;

let opts = DecodeOptions::new()
    .with_expand_paths(PathExpansionMode::Safe);
let json = decode(toon, &opts)?;
// {
//   "a": {"b": 1},
//   "c.d": 2        ← quoted key preserved
// }

CLI Usage

Basic Commands

# Auto-detect from extension
toon data.json        # Encode
toon data.toon        # Decode

# Force mode
toon -e data.txt      # Force encode
toon -d output.txt    # Force decode

# Pipe from stdin
cat data.json | toon
echo '{"name": "Alice"}' | toon -e

Encode Options

# Custom delimiter
toon data.json --delimiter pipe
toon data.json --delimiter tab

# Custom indentation
toon data.json --indent 4

# Key folding (v1.5)
toon data.json --fold-keys
toon data.json --fold-keys --flatten-depth 2

# Show statistics
toon data.json --stats

Decode Options

# Pretty-print JSON
toon data.toon --json-indent 2

# Relaxed validation
toon data.toon --no-strict

# Disable type coercion
toon data.toon --no-coerce

# Path expansion (v1.5)
toon data.toon --expand-paths

Full Example

$ echo '{"data":{"meta":{"items":["x","y"]}}}' | toon --fold-keys --stats

data.meta.items[2]: x,y

Stats:
+--------------+------+------+---------+
| Metric       | JSON | TOON | Savings |
+======================================+
| Tokens       | 13   | 8    | 38.46%  |
|--------------+------+------+---------|
| Size (bytes) | 38   | 23   | 39.47%  |
+--------------+------+------+---------+

Test Coverage

Comprehensive Test Suite

  • 109 Unit Tests: Core functionality, edge cases, error handling
  • 22 Spec Fixture Tests: Official TOON spec compliance tests
    • 11 decode fixtures (objects, arrays, primitives, validation)
    • 11 encode fixtures (formatting, delimiters, arrays)
  • 13 Key Folding Tests: v1.5 feature coverage
  • 11 Path Expansion Tests: v1.5 feature coverage
  • Integration Tests: Real-world scenarios
  • Doc Tests: All examples in documentation tested

Running Tests

# Run all tests
cargo test

# Run specific test suite
cargo test --test spec_fixtures
cargo test --lib folding
cargo test --lib expansion

# With output
cargo test -- --nocapture

# Release mode
cargo test --release

Error Handling

All operations return Result<T, ToonError> with descriptive error messages:

use toon_format::{decode_strict, ToonError};

match decode_strict("items[3]: a,b") {
    Ok(value) => println!("Success: {:?}", value),
    Err(ToonError::LengthMismatch { expected, found, .. }) => {
        eprintln!("Array length mismatch: expected {}, found {}", expected, found);
    }
    Err(e) => eprintln!("Error: {}", e),
}

Error Types

  • ParseError - Syntax errors with line/column info
  • LengthMismatch - Array length doesn't match header
  • TypeMismatch - Unexpected value type
  • InvalidStructure - Malformed TOON structure
  • SerializationError / DeserializationError - Conversion failures

Examples

See the examples/ directory for more:

  • basic.rs - Simple encode/decode
  • options.rs - Custom configuration
  • folding.rs - Key folding examples
  • expansion.rs - Path expansion examples
  • cli_usage.sh - CLI examples

Resources


Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Development

# Clone the repository
git clone https://github.com/your-org/toon-rust.git
cd toon-rust

# Run tests
cargo test --all

# Run lints
cargo clippy -- -D warnings

# Format code
cargo fmt

# Build docs
cargo doc --open

License

MIT License © 2025-PRESENT Johann Schopplich and Shreyas K S