CSV represents one table well. A catalog in which each book has several authors is not one table unless you flatten the relationship or split it across records. Token-Oriented Object Notation (TOON)1 can retain that nesting while using tabular rows for uniform arrays.
The unit spec in spec/sorbet/toon/books_serialization_spec.rb uses these Sorbet types:
class Author < T::Struct
prop :name, String
prop :notable_work, String
end
class Book < T::Struct
prop :title, String
prop :published_year, Integer
prop :authors, T::Array[Author]
end
Sorbet::Toon.encode produces the following nested representation:
catalog[2]:
- title: Distributed Systems
published_year: 2014
authors[2]{name,notable_work}:
Leslie Lamport,Paxos
Nancy Lynch,FLP result
- title: Programming Languages
published_year: 2003
authors[1]{name,notable_work}:
Benjamin Pierce,TAPL
authors[2]{name,notable_work} declares the row count and columns for the nested author array. The book fields remain grouped with that array. Plain CSV would need an agreed flattening scheme, repeated book columns, or a second table with join keys.
Decoding the Structure
Sorbet::Toon.decode returns Ruby primitives by default. Pass a struct class or signature when the caller needs reconstructed Sorbet objects. Inside DSPy.rb, data_format: :toon supplies the output signature to the decoder before Predict constructs its prediction.
class SummarizeBookCatalog < DSPy::Signature
description 'Summarize a catalog of books and their authors'
input do
const :catalog, T::Array[Book]
end
output do
const :highlights, String
const :featured_authors, T::Array[Author]
end
end
DSPy.configure do |c|
c.lm = DSPy::LM.new(
'gemini/gemini-2.5-flash',
api_key: ENV.fetch('GEMINI_API_KEY'),
schema_format: :baml,
data_format: :toon
)
end
librarian = DSPy::Predict.new(SummarizeBookCatalog)
catalog_summary = librarian.call(catalog: sample_books)
puts catalog_summary.highlights
catalog_summary.featured_authors.each { |author| puts author.name }
This path uses prompt-rendered TOON rather than provider-native JSON structured output. It removes the need for application-specific CSV flattening, but it still depends on the model returning valid TOON that matches the signature.
Choosing Between Them
Use CSV for a genuinely flat table, especially when existing systems already produce and consume it. Use TOON when one payload mixes objects, nested arrays, and repeated records and you want one representation that preserves those boundaries.
Neither format guarantees better model output. Compare token counts and evaluation results on the payloads and models that matter to the application.
Related Resources
-
See Compact Schemas and Payloads with BAML and TOON for the DSPy.rb configuration. ↩