🚀 Introducing zio-toon: Token-Efficient Serialization for Scala and ZIO

In the era of Large Language Models and token-conscious applications, every character counts. Today I’m excited to introduce zio-toon, a Scala 3 library that brings TOON (Token-Oriented Object Notation) to the ZIO ecosystem with a focus on performance, type safety, and functional programming best practices.

What Makes TOON Special?

TOON is a line-oriented, indentation-based text format that dramatically reduces token usage compared to JSON — typically by 30-60%. This makes it perfect for LLM interactions where context windows and token costs are critical constraints.

But zio-toon isn’t just about token efficiency. It’s built from the ground up with ZIO’s principles: effect-oriented programming, composable dependency injection, type-safe error handling, and streaming support.

Installation

Add to your build.sbt:

libraryDependencies += "io.github.riccardomerolla" %% "zio-toon" % "0.1.8"

The Power of Tabular Arrays

Let’s start with TOON’s killer feature: tabular arrays. Consider this common scenario — you’re building a user management API that needs to efficiently transmit user data to an LLM for analysis:

import io.github.riccardomerolla.ziotoon._
import ToonValue._
import zio._

case class User(id: Int, name: String, role: String, department: String)

val users = List(
  User(1, "Alice Johnson", "Engineering Manager", "Backend"),
  User(2, "Bob Smith", "Senior Developer", "Frontend"),
  User(3, "Charlie Brown", "DevOps Engineer", "Infrastructure"),
  User(4, "Diana Prince", "Product Manager", "Product"),
  User(5, "Eve Chen", "Data Scientist", "Analytics")
)

// Convert to TOON format
val usersToon = arr(
  users.map(u => obj(
    "id" -> num(u.id),
    "name" -> str(u.name),
    "role" -> str(u.role),
    "department" -> str(u.department)
  )): _*
)

val program = for {
  encoded <- Toon.encode(obj("users" -> usersToon))
  _ <- Console.printLine(encoded)
} yield ()

This produces incredibly compact output:

users[5]{id,name,role,department}:
  1,Alice Johnson,Engineering Manager,Backend
  2,Bob Smith,Senior Developer,Frontend
  3,Charlie Brown,DevOps Engineer,Infrastructure
  4,Diana Prince,Product Manager,Product
  5,Eve Chen,Data Scientist,Analytics

Compare this to the equivalent JSON (573 characters vs 284 characters — a 50% reduction!):

{"users":[{"id":1,"name":"Alice Johnson","role":"Engineering Manager","department":"Backend"},{"id":2,"name":"Bob Smith","role":"Senior Developer","department":"Frontend"},{"id":3,"name":"Charlie Brown","role":"DevOps Engineer","department":"Infrastructure"},{"id":4,"name":"Diana Prince","role":"Product Manager","department":"Product"},{"id":5,"name":"Eve Chen","role":"Data Scientist","Analytics"}]}

Type-Safe Schema Integration

One of zio-toon’s standout features is seamless integration with zio-schema for type-safe encoding and decoding:

import zio.schema._

case class APIResponse(
  success: Boolean,
  message: String,
  data: List[User],
  metadata: ResponseMetadata
)

case class ResponseMetadata(
  timestamp: Long,
  version: String,
  requestId: String
)

object APIResponse {
  given schema: Schema[APIResponse] = DeriveSchema.gen[APIResponse]
}

object ResponseMetadata {
  given schema: Schema[ResponseMetadata] = DeriveSchema.gen[ResponseMetadata]
}

object User {
  given schema: Schema[User] = DeriveSchema.gen[User]
}

// Real-world API response encoding
val apiResponse = APIResponse(
  success = true,
  message = "Users retrieved successfully",
  data = users,
  metadata = ResponseMetadata(
    timestamp = System.currentTimeMillis(),
    version = "1.0.0",
    requestId = "req-12345"
  )
)

val schemaProgram = for {
  // Type-safe encoding - compile-time guarantees
  encoded <- ToonSchemaService.encode(apiResponse)
  _ <- Console.printLine("Encoded response:")
  _ <- Console.printLine(encoded)
  
  // Type-safe decoding - returns APIResponse, not generic JSON
  decoded <- ToonSchemaService.decode[APIResponse](encoded)
  _ <- Console.printLine(s"Decoded ${decoded.data.length} users")
} yield decoded

// Provide the necessary layers
val layers = ToonJsonService.live >>> ToonSchemaService.live

schemaProgram.provide(layers)

Streaming for Large Datasets

When dealing with large datasets — imagine processing millions of log entries or financial transactions — memory efficiency becomes crucial. zio-toon’s streaming support lets you process data without loading everything into memory:

import zio.stream._

case class LogEntry(
  timestamp: Long,
  level: String,
  service: String,
  message: String,
  userId: Option[String]
)

// Simulate a large log file processing scenario
val logStream: ZStream[Any, Nothing, LogEntry] = ZStream.fromIterable(
  (1 to 1000000).map { i =>
    LogEntry(
      timestamp = System.currentTimeMillis() + i,
      level = if (i % 100 == 0) "ERROR" else if (i % 10 == 0) "WARN" else "INFO",
      service = s"service-${i % 5}",
      message = s"Processing request $i",
      userId = if (i % 3 == 0) Some(s"user-${i % 1000}") else None
    )
  }
)

// Convert LogEntry to ToonValue
def logEntryToToon(entry: LogEntry): ToonValue = obj(
  "timestamp" -> num(entry.timestamp),
  "level" -> str(entry.level),
  "service" -> str(entry.service),
  "message" -> str(entry.message),
  "userId" -> entry.userId.map(str).getOrElse(ToonValue.Null)
)

val streamingProgram = for {
  // Process logs in batches, memory-efficient
  _ <- Console.printLine("Processing large log dataset...")
  
  processed <- ToonStreamService.encodeStream(
    logStream.map(logEntryToToon)
  ).take(100) // Take first 100 for demo
   .runCollect
  
  _ <- Console.printLine(s"Processed ${processed.length} log entries")
  
  // Stream back to objects for analysis
  decoded <- ToonStreamService.decodeStream(
    ZStream.fromIterable(processed)
  ).runCollect
  
  errorCount = decoded.count {
    case obj if obj.fields.exists(_._2.toString.contains("ERROR")) => true
    case _ => false
  }
  
  _ <- Console.printLine(s"Found $errorCount error entries")
} yield ()

val streamLayers = (ToonEncoderService.live ++ ToonDecoderService.live) >+> ToonStreamService.live
streamingProgram.provide(streamLayers)

Resilient Error Handling with Retry Policies

Real-world applications need robust error handling. zio-toon provides built-in retry policies for various failure scenarios:

import io.github.riccardomerolla.ziotoon.ToonRetryPolicy._
import io.github.riccardomerolla.ziotoon.ToonRetryOps._

// Simulate an unreliable data source
def unreliableDataSource: ZIO[Any, Throwable, String] = 
  Random.nextBoolean.flatMap { reliable =>
    if (reliable) ZIO.succeed("""
      metrics[3]{name,value,unit}:
        cpu_usage,85.2,percent
        memory_usage,2048,mb
        disk_io,150,ops_per_sec
    """)
    else ZIO.fail(new RuntimeException("Network timeout"))
  }

val resilientProgram = for {
  _ <- Console.printLine("Fetching metrics with retry policy...")
  
  // Smart retry: only retries recoverable errors
  rawData <- unreliableDataSource
    .retry(smartRetry)
    .timeoutFail(new RuntimeException("Total timeout"))(30.seconds)
  
  // Decode with additional retry for parse errors
  metrics <- ToonDecoderService.decode(rawData)
    .retryDefault  // Built-in exponential backoff
    .catchAll {
      case ToonError.ParseError(msg) => 
        Console.printLine(s"Parse error: $msg") *> 
        ZIO.succeed(ToonValue.Null)
      case error => 
        Console.printLine(s"Unrecoverable error: ${error.message}") *>
        ZIO.fail(error)
    }
  
  _ <- Console.printLine("Metrics retrieved successfully!")
} yield metrics

resilientProgram.provide(ToonDecoderService.live)

JSON Integration and Token Savings Analysis

For applications migrating from JSON or needing interoperability, zio-toon provides seamless conversion with detailed analytics:

case class PerformanceReport(
  service: String,
  metrics: Map[String, Double],
  alerts: List[String],
  recommendations: List[String]
)

val jsonReport = """
{
  "service": "user-api",
  "metrics": {
    "response_time": 245.5,
    "error_rate": 0.02,
    "throughput": 1250.8,
    "cpu_usage": 78.2
  },
  "alerts": ["High CPU usage detected", "Response time above threshold"],
  "recommendations": ["Scale up instances", "Optimize database queries", "Enable caching"]
}
"""

val migrationProgram = for {
  _ <- Console.printLine("Converting JSON to TOON...")
  
  // Parse JSON to ToonValue
  toonValue <- ToonJsonService.fromJson(jsonReport)
  
  // Encode to TOON format
  toonString <- ToonEncoderService.encode(toonValue)
  _ <- Console.printLine("TOON format:")
  _ <- Console.printLine(toonString)
  
  // Calculate token savings
  savings <- ToonJsonService.calculateSavings(toonValue)
  _ <- Console.printLine(f"\n📊 Token Analysis:")
  _ <- Console.printLine(f"JSON size: ${savings.jsonSize} characters")
  _ <- Console.printLine(f"TOON size: ${savings.toonSize} characters")
  _ <- Console.printLine(f"Savings: ${savings.savingsPercent}%.1f%% (${savings.savings} characters)")
  
  // Verify round-trip integrity
  backToJson <- ToonJsonService.toJson(toonValue)
  _ <- Console.printLine("\nâś… Round-trip conversion successful")
} yield savings

val jsonLayers = ToonEncoderService.live ++ ToonDecoderService.live ++ ToonJsonService.live
migrationProgram.provide(jsonLayers)

Why ZIO Makes the Difference

zio-toon showcases ZIO’s power through several key patterns:

1. Effects as Blueprints

All operations are pure descriptions that compose beautifully:

val pipeline = for {
  raw <- fetchData
  parsed <- ToonDecoderService.decode(raw)
  validated <- validateBusinessRules(parsed)
  enriched <- enrichWithMetadata(validated)
  saved <- persistToDatabase(enriched)
} yield saved

2. Service Pattern with Dependency Injection

Clean separation of concerns with testable components:

// Custom encoder with business logic
class BusinessToonEncoder(
  validator: ValidationService,
  logger: LoggingService
) extends ToonEncoderService {
  
  def encode(value: ToonValue): UIO[String] =
    for {
      _ <- logger.info("Encoding business object")
      validated <- validator.validate(value)
      encoded <- ToonEncoderService.encode(validated)
      _ <- logger.debug(s"Encoded ${encoded.length} characters")
    } yield encoded
}

val businessLayer = ZLayer.fromFunction(BusinessToonEncoder.apply)

3. Composable Error Handling

Type-safe error management without exceptions:

sealed trait BusinessError extends Throwable
case class ValidationError(field: String, reason: String) extends BusinessError
case class EncodingError(underlying: ToonError) extends BusinessError

def processUserData(userData: String): ZIO[ToonDecoderService, BusinessError, ProcessedUser] =
  for {
    parsed <- ToonDecoderService.decode(userData)
      .mapError(EncodingError.apply)
    
    validated <- validateUser(parsed)
      .mapError(ValidationError.apply)
    
    processed <- processBusinessLogic(validated)
  } yield processed

Real-World Performance Benefits

Based on the included JMH benchmarks, zio-toon delivers significant advantages:

Getting Started

zio-toon is designed for gradual adoption. You can start with the pure API for simple use cases and gradually migrate to the full ZIO effect system as your needs grow:

// Simple start - pure API
val result = Toon.encode(myData)

// Growing complexity - effect API  
val program = Toon.encode(myData).provide(Toon.live)

// Full power - streaming, retry policies, custom layers
val production = complexPipeline
  .retry(smartRetry)
  .provide(fullProductionLayers)

What’s Next?

zio-toon represents a new approach to serialization in the Scala ecosystem — one that prioritizes efficiency, type safety, and functional programming principles. Whether you’re building LLM-integrated applications, high-performance APIs, or data processing pipelines, zio-toon provides the tools you need.

The library follows the official TOON Format Specification v2.0 and includes comprehensive test coverage, JMH performance benchmarks, and real-world examples.

Try It Today

Check out the project:

zio-toon is production-ready with 93+ unit tests, property-based testing, and integration tests. It demonstrates ZIO best practices while solving real problems in the era of token-conscious computing.

Built with ❤️ for the Scala and ZIO communities. Questions? Open an issue or discussion on GitHub!

zio-toon: Where functional programming meets efficient serialization. 🚀