PicoMsgPack 2026.4.2
See the version list below for details.
dotnet add package PicoMsgPack --version 2026.4.2
NuGet\Install-Package PicoMsgPack -Version 2026.4.2
<PackageReference Include="PicoMsgPack" Version="2026.4.2" />
<PackageVersion Include="PicoMsgPack" Version="2026.4.2" />
<PackageReference Include="PicoMsgPack" />
paket add PicoMsgPack --version 2026.4.2
#r "nuget: PicoMsgPack, 2026.4.2"
#:package PicoMsgPack@2026.4.2
#addin nuget:?package=PicoMsgPack&version=2026.4.2
#tool nuget:?package=PicoMsgPack&version=2026.4.2
PicoSerDe
AOT-first, reflection-free serialization framework. Five formats, one
unified API. Source-generated ref struct readers/writers with zero heap
allocation on the hot path — deployable under NativeAOT and trimming where
many serialization libraries cannot run.
Modules
| Format | Package | Status | AOT | Readme |
|---|---|---|---|---|
| JSON | PicoJetson | ✅ Production | ✅ | → |
| MessagePack | PicoMsgPack | ✅ Production | ✅ | → |
| INI | PicoIni | ✅ Production | ✅ | → |
| TOML | PicoToml | ✅ Production | ✅ | → |
| YAML | PicoYaml | ✅ Production | ✅ | → |
PicoYaml is the only AOT-compatible YAML library on .NET.
Test Coverage
781 tests across all 6 modules, with cross-validation against 5 competitor libraries:
| Module | Tests | Competitor | Cross-Validation |
|---|---|---|---|
| PicoJetson | 324 | System.Text.Json | ✅ bidirectional, all 19 property types |
| PicoToml | 90 | Tomlyn | ✅ bidirectional, 20 property types, NestedList via [[key]] |
| PicoYaml | 90 | YamlDotNet | ✅ bidirectional, 19 property types, DateOnly/TimeOnly conerters |
| PicoIni | 104 | Microsoft.Extensions.Configuration.Ini | ✅ bidirectional, 16 property types |
| PicoMsgPack | 113 | MessagePack-CSharp | ✅ map/array dual-format, 14 property types |
| PicoSerDe.Core | 36 | — | — |
Performance Summary
Numbers below are PicoSerDe on NativeAOT vs competitors on JIT — the competitors cannot run under NativeAOT at all. In a JIT environment, mature reflection-based parsers may still be faster; PicoSerDe's advantage is guaranteed deployability under trimming and self-contained publishing, not peak JIT throughput.
Benchmarks: AOT self-contained, .NET 10, 100K iterations, win-x64.
| Module | vs Competitor | Avg Speedup | Competitor AOT? |
|---|---|---|---|
| PicoJetson | System.Text.Json | 1.35x | ✅ |
| PicoMsgPack | MessagePack-CSharp | 1.40x | ❌ |
| PicoIni | ini-parser | 0.12x | ❌ |
| PicoToml | Tommy | 0.30x | ❌ |
| PicoYaml | — | — | ❌ |
JSON/MessagePack are faster than or competitive with JIT-based alternatives even in AOT mode. INI/TOML/YAML prioritize correct, reflection-free parsing over peak throughput — their JIT competitors benefit from years of runtime-level optimizations (cached keys, direct span writes, dynamic code gen) that are incompatible with NativeAOT. PicoSerDe is the only option that runs at all in a fully-trimmed, self-contained NativeAOT deployment for these formats.
Design
// One API across all formats
JsonSerializer.Serialize<T>(value) // → byte[] via PicoJetson
MsgPackSerializer.Deserialize<T>(data) // T ← byte[] via PicoMsgPack
IniSerializer.Serialize(config) // → string via PicoIni
Attribute-Driven Registration
PicoSerDe source generators discover types through four independent pipelines:
- Usage-driven — calling
Serialize<T>()orDeserialize<T>()triggers generation forT - Generic attribute —
[PicoSerializable]marks a type for all referenced format modules - Format-specific attribute —
[PicoJsonSerializable]/[PicoIniSerializable]/ etc. marks a type for one format - Shorthand attribute —
[GenerateSerializer(typeof(T))]for central registration
// All referenced formats generate serializers
[PicoSerializable]
public class UserDto { public string Name { get; set; } }
// JSON only (PicoJetson)
[PicoJsonSerializable]
public class JsonOnlyDto { public string Label { get; set; } }
// Indirect — target type from any assembly
[PicoIniSerializable(typeof(ExternalLibrary.SharedDto))]
class Config { }
// Shorthand — equivalent to PicoSerializable(typeof(T))
[GenerateSerializer(typeof(UserDto))]
[GenerateSerializer(typeof(ProductDto))]
class PicoSerDeConfig { }
| Attribute | Scope | Defined in |
|---|---|---|
[PicoSerializable] |
All formats — direct or typeof(T) |
PicoSerDe.Core |
[GenerateSerializer] |
Shorthand for PicoSerializable(typeof(T)) |
PicoSerDe.Core |
[PicoJsonSerializable] |
JSON only | PicoJetson |
[PicoIniSerializable] |
INI only | PicoIni |
[PicoTomlSerializable] |
TOML only | PicoToml |
[PicoMsgPackSerializable] |
MsgPack only | PicoMsgPack |
[PicoYamlSerializable] |
YAML only | PicoYaml |
No attributes are required for basic usage — calling Serialize<T>() automatically triggers generation.
Key Features
Polymorphic Deserialization (Type Discriminator)
Base types declare derived types at compile time. Zero reflection, AOT-safe. Since v2026.3.0.
[PicoSerializable]
[PicoDerivedType(typeof(MessageEntry), "message")]
[PicoDerivedType(typeof(CompactionEntry), "compaction")]
abstract class SessionEntry { }
class MessageEntry : SessionEntry { public string Content { get; set; } = string.Empty; }
class CompactionEntry : SessionEntry { public int From { get; set; } }
var json = """{"$type":"message","Content":"hello"}"""u8;
var result = JsonSerializer.Deserialize<SessionEntry>(json);
// result is MessageEntry at runtime
| Feature | Support |
|---|---|
| Serialization + Deserialization | ✅ v2026.3.0 |
| Streaming (PipeReader) | ✅ v2026.3.2 |
| Base class properties | ✅ v2026.3.3 |
[JsonConstructor] on derived types |
✅ |
| Record derived types | ✅ v2026.3.23 |
| Complex/collection ctor params | ✅ v2026.3.24 |
| TOML / YAML poly support | ✅ v2026.3.24 |
| INI / MsgPack poly support | ✅ v2026.4.0 |
DOM Layer (PicoDocument / PicoElement)
Schema-less JSON inspection without System.Text.Json. Zero-copy.
var doc = PicoDocument.Parse("""{"name":"Alice","age":30}"""u8.ToArray());
var name = doc.RootElement["name"].GetString(); // "Alice"
var ok = doc.RootElement.TryGetProperty("age", out _); // true
bool valid = PicoDocument.IsValid("{}"u8); // true
// Numeric access
long big = doc.RootElement["count"].GetInt64();
double d = doc.RootElement["score"].GetDouble();
if (doc.RootElement["age"].TryGetInt32(out int age))
Console.WriteLine(age);
C# Records
record and record struct types are fully supported. Primary constructor auto-detected — no [JsonConstructor] needed. init-only properties work correctly.
Top-Level Arrays
Serialize<T[]>(...) / Deserialize<T[]>(...) and streaming DeserializeFromStreamAsync<T[]>(stream) work directly.
Three-Layer Test Structure
PicoJetson tests are split into Unit / Integration / Functional projects with clear boundaries.
No non-generic
Serialize(Type, object?)overloads. PicoSerDe is designed for AOT-first usage where all types are known at compile time.SerRegistry<TFormat, T>static fields (PicoSerDe.Core) are shared across assemblies and provide faster lookup than aConcurrentDictionary<Type, ...>. Framework wrappers should call the generic API internally — the type's serializer is guaranteed to be registered viaModuleInitializeras long as the type was discovered by any pipeline (usage-driven, attribute, or shorthand).
┌──────────────────────────────────────────────┐
│ User Code │
└──────────────────┬───────────────────────────┘
│ Static SerRegistry<TFormat, T>
┌──────────────────▼───────────────────────────┐
│ PicoSerDe.Core │
│ ISerializer<T> │ IDeserializer<T> │
│ SerRegistry │ DesRegistry │
│ TokenType │ SimdHelpers (Vector128) │
│ TextHelpers │ SerializerExtensions │
└────┬────────┬─────────┬─────────┬─────────┬──┘
│ │ │ │ │
PicoJetson PicoIni PicoMsgPack PicoToml PicoYaml
││ ││ ││ ││ ││
.Gen .Gen .Gen .Gen .Gen
- Dual-package: each format → runtime library (net10.0) + source generator (netstandard2.0)
ref structreaders/writers — stack-allocated, zero heap allocation on hot path- Static
SerRegistry<TFormat, T>— per-format registries in PicoSerDe.Core; JIT/AOT inlineable, no dictionary lookups file structgenerated implementations — devirtualization without sealed class overhead- Ref struct serialization —
ref structtypes are supported as serializable types across all 5 formats. Source-generator-generated static methods + delegate dispatch bypass theISerializer<T>interface constraint. JsonOptions— runtime configuration (indentation, naming policy, ignore conditions, etc.) flowing through ThreadStatic to SG-generated code- Polymorphic deserialization — type discriminator dispatch via
[PicoDerivedType]; serialization + deserialization + streaming (v2026.3.0); record types (v2026.3.23); TOML/YAML poly (v2026.3.24); INI/MsgPack poly (v2026.4.0) - Anonymous type serialization —
Serialize(new { A = 1, B = "x" })with nested types, collections,PropertyNamingPolicy.CamelCase,DefaultIgnoreCondition.WhenWritingNull, andMaxDepthenforcement. Works across all 5 formats via C# 12 interceptors + unsafe field access (serialization only, C# 12+,<AllowUnsafeBlocks>true</AllowUnsafeBlocks>) (v2026.7.22) PicoDocument/PicoElement— zero-copy JSON DOM for schema-less inspection (v2026.3.4)- C# records — primary constructor auto-detection,
init-only support (v2026.3.3); poly+record (v2026.3.23); complex/collection ctor params (v2026.3.24) - Top-level arrays —
Serialize<T[]>()/Deserialize<T[]>()with streaming (v2026.3.2)
PicoJetson JsonOptions
// Compact (default) — optimal for data transfer
byte[] data = JsonSerializer.SerializeToUtf8Bytes(model);
// Human-readable
byte[] data = JsonSerializer.SerializeToUtf8Bytes(model,
new JsonOptions { Indented = true });
// CamelCase naming
byte[] data = JsonSerializer.SerializeToUtf8Bytes(model,
new JsonOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
// Skip null properties
byte[] data = JsonSerializer.SerializeToUtf8Bytes(model,
new JsonOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
// Allow NaN/Infinity
byte[] data = JsonSerializer.SerializeToUtf8Bytes(model,
new JsonOptions { NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals });
Available options:
| Option | Default | Description |
|---|---|---|
Indented |
false |
Human-readable indented output |
MaxDepth |
63 |
Maximum nesting depth |
PropertyNamingPolicy |
null |
Naming policy: CamelCase, SnakeCaseLower, KebabCaseLower, PascalCase |
DefaultIgnoreCondition |
Never |
Skip null/default properties: WhenWritingNull, WhenWritingDefault |
NumberHandling |
Strict |
Allow named floats: AllowNamedFloatingPointLiterals |
PropertyNameCaseInsensitive |
false |
Case-insensitive property matching (default is already case-insensitive) |
AllowTrailingCommas |
false |
Accept trailing commas in objects/arrays |
ReadCommentHandling |
Disallow |
Skip // and /* */ comments |
UnmappedMemberHandling |
Skip |
Throw on unknown properties: Disallow |
Null handling across formats
Every format's options class (JsonOptions, YamlOptions, TomlOptions, IniOptions, MsgPackOptions) exposes DefaultIgnoreCondition, but what "writing a null" means depends on the wire format:
| Format | Default (Never) |
WhenWritingNull |
|---|---|---|
| JSON | "key":null written |
omitted |
| MsgPack | nil written (map count adjusts automatically) |
omitted |
| TOML / INI | omitted — these formats have no null literal | omitted |
| YAML | omitted — the reader has no null-literal support yet; writing key: would read back as a default value and break round-trip fidelity |
omitted |
The matrix applies to every emit path — top-level members, nested objects, collection elements, nullable collections, and polymorphic dispatch — and is locked by cross-format regression tests (IgnoreConditionMatrixTests).
Per-property control is available via the cross-format [PicoIgnore] attribute (PicoSerDe.Core):
[PicoIgnore] // stripped everywhere (write + read)
public string Internal { get; set; } = "";
[PicoIgnore(Condition = PicoIgnoreCondition.WhenWritingNull)] // omitted only when null, regardless of global options
public string? Note { get; set; }
[PicoIgnore(Condition = PicoIgnoreCondition.Never)] // exempt from the global DefaultIgnoreCondition
public string? Pinned { get; set; }
Conditions affect serialization only — deserialization still maps conditional properties. Format-specific markers ([JsonIgnore], [YamlIgnore], …) remain single-format unconditional ignores.
Custom serializers for nested types
Register applies at the top level only. To also override T wherever it appears as a nested value (object property, list element, dictionary value), use RegisterCustom — available on JSON and MessagePack:
JsonSerializer.RegisterCustom(new MySerializer(), new MyDeserializer());
// Outer { Foo Inner } now serializes Inner with MySerializer too.
// Deserialization override applies at the top level only.
Packages
| Package | NuGet |
|---|---|
PicoSerDe.Core |
|
PicoJetson / .Gen |
|
PicoMsgPack / .Gen |
|
PicoIni / .Gen |
|
PicoToml / .Gen |
|
PicoYaml / .Gen |
CI/CD
| Target | Runner |
|---|---|
| win-x64 | windows-latest |
| win-arm64 | windows-latest |
| linux-x64 | ubuntu-latest |
| linux-arm64 | ubuntu-24.04-arm |
| osx-arm64 | macos-latest |
Every push: build + test (500+ tests) + 5 benchmarks smoke + 5 AOT sample publishes.
Release: v* tag → packs 11 packages in dependency order → NuGet.org.
Comparison
| PicoSerDe | S.T.Json | YamlDotNet | VYaml | MsgPack-CS | Tommy | |
|---|---|---|---|---|---|---|
| Formats | 5 | 1 | 1 | 1 | 1 | 1 |
| AOT | ✅ | ✅ | ❌ | ⚠️ | ❌ | ❌ |
| Zero-reflection | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Zero annotations | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| ref struct readers | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| SIMD | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| JSON DOM | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Polymorphic | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- PicoSerDe.Core (>= 2026.4.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2026.10.0 | 91 | 9/18/2026 |
| 2026.9.14 | 86 | 9/14/2026 |
| 2026.5.3 | 89 | 9/11/2026 |
| 2026.5.2 | 89 | 9/11/2026 |
| 2026.5.1 | 94 | 9/9/2026 |
| 2026.5.0 | 110 | 8/24/2026 |
| 2026.4.11 | 114 | 8/23/2026 |
| 2026.4.10 | 103 | 8/23/2026 |
| 2026.4.9 | 104 | 8/23/2026 |
| 2026.4.8 | 101 | 8/23/2026 |
| 2026.4.7 | 125 | 8/16/2026 |
| 2026.4.6 | 101 | 8/13/2026 |
| 2026.4.5 | 110 | 8/13/2026 |
| 2026.4.4 | 105 | 8/11/2026 |
| 2026.4.3 | 97 | 8/11/2026 |
| 2026.4.2 | 118 | 7/22/2026 |
| 2026.4.1 | 116 | 7/22/2026 |
| 2026.4.0 | 117 | 7/18/2026 |
| 2026.3.25 | 118 | 7/13/2026 |
| 2026.3.24 | 120 | 7/13/2026 |