ToonNet vs Toon.Format
Toon.Format is the official .NET implementation of TOON, from the authors of the spec. Both libraries implement spec v4.4 and write the same text; ToonNet does it 8–27x faster when serializing and 2.4–8x faster when deserializing, with 6–55x less memory (100 and 10,000 items, with reflection or generated code; the lower ends are reflection with 100 items), and adds a source generator, NativeAOT support, exact decimals and ASP.NET Core integration.
Same Output
- Both pass all 564 official conformance fixtures.
- 20,000 random JSON documents (tricky strings and keys, numbers, nested and uniform objects, keyed objects, all three delimiters) encode to byte-identical text, and each library reads the other's text back to the same JSON.
- Typed data – flat records, nested objects with arrays, nested field groups and keyed tables – serializes to identical text and reads back to the same values.
tests/ToonNet.Compatibility.Tests checks this on every build. To run the full report yourself:
dotnet run -c Release --project benchmark/ToonNet.Comparison -- parity
The remaining differences are in how .NET values are mapped to the data model, which spec §3 leaves to implementations:
| Value | ToonNet | Toon.Format 1.0.0 |
|---|---|---|
enum DayOfWeek.Friday | Day: Friday | Day: 5 |
decimal 12345678901234567.89 | 12345678901234567.89 | 12345678901234568 (rounded through double) |
decimal 123456789012345678901234567 | 1.23456789012345678901234567e+26 | 1.2345678901234568e+26 |
ulong.MaxValue | 18446744073709551615 | 18446744073709552000 (cannot be read back into a ulong) |
Toon.Format converts numbers to double, so values beyond its precision do not survive a round trip (spec §2 asks
that decode(encode(x)) equals x); ToonNet keeps every digit of decimal, long and ulong values.
Performance
10,000 items, Apple M3 Max, .NET 10.0.5, BenchmarkDotNet 0.15.8. "ToonNet" uses [ToonSerializable] models (generated
code), "reflection" the same models without it; System.Text.Json writes and reads the same data as JSON, for
reference. Setup checks that both TOON libraries produce identical text first.
Serialize
| Data | Toon.Format | ToonNet | ToonNet (reflection) | System.Text.Json |
|---|---|---|---|---|
| Employees (tabular) | 46.03 ms · 51.8 MB | 2.00 ms · 1.7 MB (23.0x faster) | 2.64 ms · 1.7 MB | 2.32 ms · 3.3 MB |
| Orders (nested objects, arrays) | 75.27 ms · 85.0 MB | 2.78 ms · 4.6 MB (27.1x faster) | 4.03 ms · 4.6 MB | 2.87 ms · 4.1 MB |
| Products (nested field groups) | 51.14 ms · 53.1 MB | 2.71 ms · 1.0 MB (18.9x faster) | 3.14 ms · 1.0 MB | 2.97 ms · 2.7 MB |
| Servers (keyed table) | 23.90 ms · 27.6 MB | 0.96 ms · 1.0 MB (24.9x faster) | 1.02 ms · 1.0 MB | 0.95 ms · 1.5 MB |
Deserialize
| Data | Toon.Format | ToonNet | ToonNet (reflection) | System.Text.Json |
|---|---|---|---|---|
| Employees (tabular) | 31.58 ms · 34.3 MB | 5.27 ms · 2.3 MB (6.0x faster) | 5.54 ms · 2.3 MB | 4.19 ms · 2.5 MB |
| Orders (nested objects, arrays) | 74.15 ms · 78.3 MB | 9.09 ms · 5.4 MB (8.2x faster) | 10.35 ms · 5.4 MB | 8.49 ms · 5.7 MB |
| Products (nested field groups) | 34.47 ms · 28.8 MB | 5.10 ms · 3.4 MB (6.8x faster) | 5.78 ms · 3.4 MB | 6.35 ms · 3.5 MB |
| Servers (keyed table) | 15.93 ms · 21.9 MB | 3.62 ms · 3.1 MB (4.4x faster) | 3.70 ms · 3.1 MB | 2.82 ms · 2.5 MB |
With 100 items ToonNet serializes 9.9–12.7x and deserializes 2.8–3.3x faster than Toon.Format. The overloads that take
a ToonTypeInfo (no reflection, for trimming and NativeAOT) are as fast as "ToonNet": 18.9–27x and 4.8–8.3x faster
than Toon.Format with 10,000 items. ToonNet writes about as fast as System.Text.Json (0.86–1.02x its time) and
takes 0.80–1.28x its time when reading 10,000 items (1.04–1.57x with 100; less for nested field groups), while its TOON output is much smaller than the JSON. Full results, including 100 items: benchmark/ToonNet.Comparison/RESULTS.md.
Run them on your hardware with dotnet run -c Release --project benchmark/ToonNet.Comparison -- --filter "*".
Why: Toon.Format serializes a value with System.Text.Json into a JsonNode tree and encodes the tree, and decodes
text into a JsonNode tree that System.Text.Json then binds. ToonNet writes objects straight to TOON text and reads
them straight from it, with code generated for each type.
Features
| ToonNet | Toon.Format 1.0.0 | |
|---|---|---|
| TOON spec | v4.4, 564/564 fixtures | v4.4, 564/564 fixtures |
| Targets | .NET 8, .NET 10 | .NET 10, .NET Standard 2.0 |
| Typed API | ToonSerializer.Serialize/Deserialize (text, streams, files) | ToonEncoder.Encode<T>/ToonDecoder.Decode<T> via System.Text.Json |
| Source generator | ✅ [ToonSerializable] | – |
| NativeAOT / trimming | ✅ ToonSerializer with generated type infos, tested in CI | – |
| Document model | ToonDocument / ToonValue | JsonNode |
| Options | indent, delimiter, strict, naming policies, null handling, read-only properties, unknown keys, required members, declared or runtime types, converters, max depth | indent, delimiter, strict |
Exact decimal / ulong | ✅ | rounded through double |
| Multi-document streaming | ✅ SerializeDocumentsAsync / DeserializeDocumentsAsync | single document per stream |
| Queries | ✅ JSONPath (RFC 9535, 715/715 compliance cases) over trees and text, string/type/aggregate and your own functions; DeserializeAt, DeserializeAll | – |
| Business rules | ✅ ToonRuleSet: assertions, Required, Forbidden, Unique, with the path of each violation | – |
| Editing | ✅ indexers, GetValue<T>, DeepClone, DeepEquals, SetValue/RemoveAll by path, JSON Merge Patch | JsonNode |
| LLM helpers | ✅ ToonPrompt: shortened arrays, answer templates, lenient reading of answers | – |
| ASP.NET Core | input/output formatters, ToonResult, configuration provider | – |
| JSON / YAML conversion | ✅ ToonConvert, ToonYamlConvert | via JsonNode |
How ToonNet Is Built
- Straight to text -
ToonSerializerwrites objects to TOON text and reads them from it directly, without an intermediate document; the tabular forms (including nested field groups and keyed tables) are planned from the objects and written in one pass - Source Generator -
[ToonSerializable]types get generated writers and readers thatToonSerializeruses automatically: keys are encoded and matched at compile time, primitives are written without boxing - Compiled accessors - For other types, property getters/setters and constructors are compiled once per type; primitive properties are read and written through typed delegates, so their values are not boxed
- Cursor parser - One quote-aware scan per line, values read from spans, the line table and buffers rented from
ArrayPool, repeated keys and headers shared instead of allocated again - UTF-8 in and out - Bytes, spans, streams and
IBufferWriter<byte>(aPipeWriter) are read and written through pooled buffers, without intermediate strings: reading bytes allocates 18–46% less than going through a string, andSerializeToUtf8Bytesis 4–13% faster with a third of the allocation - NativeAOT and trimming -
ToonNet.Coreis annotated;ToonSerializer.Serialize(catalog, Catalog.ToonTypeInfo)and the other overloads that take a generated type info need no reflection and keep the fast path (tested with NativeAOT in CI) - .NET 8 and .NET 10 - Builds for both;
ToonNet.Corehas no package dependencies
Async I/O
- ConfigureAwait(false) - Awaited I/O calls do not capture the synchronization context
- Cancellation Support -
CancellationTokenon all async methods - Files -
SerializeToFileAsyncandToonEncoder.EncodeToFileAsyncencode in memory first and write the file only when encoding succeeded (no truncated file); the multi-document file methods use an 81,920-byte buffer - Streaming -
SerializeDocumentsAsync/DeserializeDocumentsAsyncprocess one document at a time
Thread-Safety
ToonSerializer and a shared ToonEncoder are safe to use from several threads; see Configuration for the caches and options.
See Also
- Performance Tuning: getting the most out of ToonNet
- Source Generators: generated code for
[ToonSerializable]types - Benchmark results