A high-performance ASON (Array-Schema Object Notation) serialization/deserialization library for .NET — zero-copy, SIMD-accelerated, schema-driven data format designed for LLM interactions and large-scale data transmission.
ASON separates schema from data, eliminating repetitive keys found in JSON. The schema is declared once, and data rows carry only values:
JSON (100 tokens):
{"users":[{"id":1,"name":"Alice","active":true},{"id":2,"name":"Bob","active":false}]}
ASON (~35 tokens, 65% saving):
[{id@int, name@str, active@bool}]:(1,Alice,true),(2,Bob,false)
| Aspect | JSON | ASON |
|---|---|---|
| Token efficiency | 100% | 30–70% ✓ |
| Key repetition | Every object | Declared once ✓ |
| Human readable | Yes | Yes ✓ |
| Nested structs | ✓ | ✓ |
| Type annotations | No | Optional ✓ |
| Serialization speed | 1x | ~1.2–8x faster ✓ |
| Data size | 100% | 40–60% ✓ |
Add the Ason NuGet package:
dotnet add package AsonThe published NuGet package ships a single package with assets for both net8.0 and net10.0.
If your app targets a specific runtime, you can pin it explicitly in your project file:
<TargetFramework>net8.0</TargetFramework>or:
<TargetFramework>net10.0</TargetFramework>using Ason;
record User(long Id, string Name, bool Active) : IAsonSchema
{
static readonly string[] _names = ["id", "name", "active"];
static readonly string?[] _types = ["int", "str", "bool"];
public ReadOnlySpan<string> FieldNames => _names;
public ReadOnlySpan<string?> FieldTypes => _types;
public object?[] FieldValues => [Id, Name, Active];
public static User FromFields(Dictionary<string, object?> m) =>
new(Convert.ToInt64(m["id"]), (string)m["name"]!, Convert.ToBoolean(m["active"]));
}var user = new User(1, "Alice", true);
// Encode
var s = Ason.Ason.encode(user);
// => "{id,name,active}:(1,Alice,true)"
// Encode with type annotations
var typed = Ason.Ason.encodeTyped(user);
// => "{id@int,name@str,active@bool}:(1,Alice,true)"
// Decode
var u2 = Ason.Ason.decodeWith(s, User.FromFields);
// u2 == user ✓For List<T>, ASON writes the schema once and emits each element as a compact tuple — the key advantage over JSON:
var users = new List<User> {
new(1, "Alice", true),
new(2, "Bob", false),
};
var s = Ason.Ason.encode<User>(users);
// => "[{id,name,active}]:(1,Alice,true),(2,Bob,false)"
var users2 = Ason.Ason.decodeListWith(s, User.FromFields);
// users2.Count == 2 ✓// Zero-copy binary encoding (BinaryPrimitives, no intermediate allocation)
var bin = Ason.Ason.encodeBinary(user);
var u3 = Ason.Ason.decodeBinaryWith(bin,
new[] { "id", "name", "active" },
new[] { FieldType.Int, FieldType.String, FieldType.Bool },
User.FromFields);var pretty = Ason.Ason.encodePretty(user);
// => "{id, name, active}:(1, Alice, true)"
var prettyTyped = Ason.Ason.encodePrettyTyped(user);
// => "{id@int, name@str, active@bool}:(1, Alice, true)"| Type | ASON Representation | Example |
|---|---|---|
| int | Plain number | 42, -100 |
| float | Decimal number | 3.14, -0.5 |
| bool | Literal | true, false |
| str | Unquoted or quoted | Alice, "Carol Smith" |
| Optional | Value or empty | hello or (blank) |
| List<T> | [v1,v2,v3] |
[rust,go,python] |
| Nested struct | (field1,field2) |
(Engineering,500000) |
Native Dictionary<K,V> / map fields are intentionally unsupported in the current ASON format.
If you need keyed collections, model them explicitly as entry-list arrays such as:
{attrs@[{key@str,value@int}]}:([(age,30),(score,95)])
record Dept(string Title) : IAsonSchema { /* ... */ }
record Employee(string Name, Dept Dept) : IAsonSchema { /* ... */ }
// Schema reflects nesting:
// {name@str,dept@{title@str}}:(Alice,(Engineering))// With value@{id,label}:(1,hello)
// With null@{id,label}:(1,)
{name,tags}:(Alice,[rust,go,python])
/* user list */
[{id@int, name@str, active@bool}]:(1,Alice,true),(2,Bob,false)
[{id@int, name@str, active@bool}]:
(1, Alice, true),
(2, Bob, false),
(3, "Carol Smith", true)
| Function | Description |
|---|---|
Ason.encode(T) |
Serialize struct → unannotated schema |
Ason.encodeTyped(T) |
Serialize struct → annotated schema |
Ason.encode<T>(List<T>) |
Serialize list → unannotated schema (written once) |
Ason.encodeTyped<T>(List<T>) |
Serialize list → annotated schema |
Ason.decode(string) |
Deserialize → field bag (Dictionary<string, object?>) |
Ason.decodeWith<T>(s, fn) |
Deserialize → typed T via factory |
Ason.decodeListWith<T>(s, fn) |
Deserialize → List<T> via factory |
Ason.encodeBinary(T) |
Binary encode (zero-copy BinaryPrimitives) |
Ason.decodeBinaryWith<T>(…) |
Binary decode → typed T |
Ason.encodePretty(T) |
Pretty-format encode |
Ason.encodePrettyTyped(T) |
Pretty-format with type annotations |
Run the bundled benchmark with:
dotnet run --project examples/Bench/Ason.Examples.Bench.csproj -c ReleaseHeadline numbers::
Flat struct × 500 (8 fields, vec)
Serialize: JSON 16.22ms/60784B | ASON 10.11ms(1.6x)/28327B(46.6%) | BIN 4.92ms(3.3x)/37230B(61.2%)
Deserialize: JSON 22.09ms | ASON 5.70ms(3.9x) | BIN 2.11ms(10.5x)
Actual timings vary by runtime, CPU, and whether you run Debug or Release.
- Zero key-hashing — Schema parsed once; fields mapped by position index
O(1), no per-row key string hashing. - Schema-driven parsing — Deserializer knows expected types, enabling direct parsing. CPU branch prediction hits ~100%.
- Minimal allocation — All rows share one schema reference.
ArrayPool,stackalloc,ReadOnlySpan<char>everywhere. - SIMD acceleration —
SearchValues<char>auto-selects SSE2/AVX2/AdvSimd for character scanning. - Zero-copy decode — Parsing operates directly on
ReadOnlySpan<char>, no intermediate string allocation. - Schema caching — Encoder caches schema header strings per type; decoder caches parsed field name arrays.
- Zero-boxing
WriteValues— Direct typed field writes bypassobject?[]allocation entirely.
ArrayPool<char>/ArrayPool<byte>for writer buffers — zero GC pressureThreadLocalwriter reuse for single-struct encode — no rent/return overhead- Schema header caching via
ConcurrentDictionary<Type, string> - Decoded schema caching via
ConcurrentDictionary<int, string[]> - Zero-boxing
WriteValues/WriteBinaryValuesinterface methods stackallocfor integer/float formattingReadOnlySpan<char>for all parsing — no string copiesBinaryPrimitivesfor little-endian binary I/O — direct memory operationsSearchValues<char>(.NET 8+, package targetsnet8.0andnet10.0) — hardware-accelerated character scanningref structfor decoder state — fully stack-allocated[MethodImpl(MethodImplOptions.AggressiveInlining)]on hot paths
# Basic usage
dotnet run --project examples/Basic
# Complex nested structures, escaping, 5-level deep nesting
dotnet run --project examples/Complex
# Performance benchmark (ASON vs JSON)
dotnet run --project examples/Bench -c ReleaseIf you have both target frameworks enabled locally, you can run a specific one:
dotnet run --project examples/Basic -f net8.0
dotnet run --project examples/Basic -f net10.0See the full ASON Spec for syntax rules, BNF grammar, escape rules, type system, and LLM integration best practices.
| Element | Schema | Data |
|---|---|---|
| Object | {field1@type,field2@type} |
(val1,val2) |
| Array | field@[type] |
[v1,v2,v3] |
| Object array | field@[{f1@type,f2@type}] |
[(v1,v2),(v3,v4)] |
| Nested object | field@{f1@type,f2@type} |
(v1,(v3,v4)) |
| Null | — | (blank) |
| Empty string | — | "" |
| Comment | — | /* ... */ |
MIT