SynesthesiaDev.Codon.Codon
2026.522.1
dotnet add package SynesthesiaDev.Codon.Codon --version 2026.522.1
NuGet\Install-Package SynesthesiaDev.Codon.Codon -Version 2026.522.1
<PackageReference Include="SynesthesiaDev.Codon.Codon" Version="2026.522.1" />
<PackageVersion Include="SynesthesiaDev.Codon.Codon" Version="2026.522.1" />
<PackageReference Include="SynesthesiaDev.Codon.Codon" />
paket add SynesthesiaDev.Codon.Codon --version 2026.522.1
#r "nuget: SynesthesiaDev.Codon.Codon, 2026.522.1"
#:package SynesthesiaDev.Codon.Codon@2026.522.1
#addin nuget:?package=SynesthesiaDev.Codon.Codon&version=2026.522.1
#tool nuget:?package=SynesthesiaDev.Codon.Codon&version=2026.522.1
🧬 Codon
Codon is a lightweight codec library for .NET
Codon has five packages:
Codon.BinaryCodec- for serialization to binary formatCodon.Codec- for serialization into any format using transcoders (JSON included)Codon.Optional- Optional class as replacement for nullability because nullable generics SUCK in C#Codon.IniTranscoder- Optional package that includes transcoder for .ini format
Codecs
Let's define a basic codec for Person class:
(All examples are using JsonTranscoder but any transcoder may be used)
public record Person(string Name, int Age, Optional<bool> IsAwesome)
{
public static readonly StructCodec<Person> CODEC = StructCodec.For<Person>()
.Field("name", Codecs.STRING, p => p.Name)
.Field("age", Codecs.INT, p => p.Age)
.Field("is_awesome", Codecs.BOOLEAN.Optional(), p => p.IsAwesome)
.Build((name, age, isAwesome) => new Person(name, age, isAwesome));
}
To Serialize or our Person object we can use:
var person = new Person("Silly Billy", 18, true);
var encoded = Person.Codec.Encode(JsonTranscoder.Instance, person);
Console.WriteLine(encoded.GetRawText()); // {"name":"Silly Billy","age":18,"is_awesome":true}
We can then decode the json back with:
var decoded = Person.Codec.Decode(JsonTranscoder.Instance, encoded);
Console.WriteLine(decoded); // Person { name = Silly Billy, age = 18, isAwesome = True }
You can nest codecs by referencing them in another codec. Lets add PersonalInformation class to our Person class:
public record PersonalInformation(string Address, int Height, int Weight)
{
public static readonly StructCodec<PersonalInformation> CODEC = StructCodec.For<PersonalInformation>()
.Field("address", Codecs.STRING, p => p.Address)
.Field("height", Codecs.INT, p => p.Height)
.Field("weight", Codecs.INT, p => p.Weight)
.Build((address, height, weight) => new PersonalInformation(address, height, weight));
}
Now we can reference it in our Person class just like this:
.Field("personal_information", PersonalInformation.CODEC, p => p.PersonalInformation),
.Build((name, age, someBoolean, personalInformation) => new Person(name, age, someBoolean, personalInformation));
Optional and Default fields
using Codon.Codec;
using Codon.Codec.Transcoder.Transcoders;
public record User(string id, Optional<string> displayName, int level)
{
public static readonly StructCodec<User> Codec = StructCodec.For<User>(
.Field("id", Codecs.String, u => u.id)
.Field("display_name", Codecs.String.Optional(), u => u.displayName)
.Field("level", Codecs.Int.Default(1), u => u.level)
.Build((id, displayName, level) => new User(id, displayName, level));
);
};
Missing optional field decodes to Optional.Empty
(Note: Optional class is a custom class for wrapping null values because generics in C# suck and don't pass nullable generics properly)
var json = JsonDocument.Parse("{\"id\":\"u1\"}").RootElement;
var decodedUser = User.Codec.Decode(JsonTranscoder.Instance, json);
Console.WriteLine(decodedUser) // User { id = "u1", displayName = null, level = 1 }
Lists and Maps
var listCodec = Codecs.Int.List(); // ICodec<List<int>>
var mapCodec = Codecs.String.MapTo(Codecs.Int); // ICodec<Dictionary<string,int>>
var list = new List<int> { 1, 2, 3 };
var map = new Dictionary<string, int> { { "a", 1 }, { "b", 2 } };
var encodedList = listCodec.Encode(t, list);
var encodedMap = mapCodec.Encode(t, map);
Enums
enum Color { Red, Green, Blue }
var colorCodec = Codecs.Enum<Color>();
var encodedColor = colorCodec.Encode(t, Color.Green);
Transformative codecs
Wrap one codec to expose values of another type using two conversion functions via the .Transform<Out>(to, from)
helper on the inner codec:
// Store an int using an inner string codec (int <-> string)
var intAsString = Codecs.String.Transform<int>(
to: s => int.Parse(s),
from: i => i.ToString()
);
var encoded = intAsString.Encode(t, 12345);
var decoded = intAsString.Decode(t, enc); // 12345
Polymorphic Unions (discriminator based)
using Codon.Codec;
using Codon.Codec.Transcoder;
using Codon.Codec.Transcoder.Transcoders;
abstract record Shape;
record Rect(int w, int h) : Shape;
enum Kind { Rect }
// Base codec for Rect
var rectCodec = StructCodec.For<Rect>(
.Field("w", Codecs.Int, r => r.w)
.Field("h", Codecs.Int, r => r.h)
.Build((w, h) => new Rect(w, h));
// Sometimes you may need a small adapter to upcast StructCodec<Rect> to StructCodec<Shape>
StructCodec<Shape> Upcast(StructCodec<Rect> inner) => new UpcastStructCodec<Shape, Rect>(inner, s => (Rect)s, r => r);
// Build a union codec using an enum discriminator and `.Union(...)` helper
var shapeCodec = ((ICodec<Kind>)Codecs.Enum<Kind>()).Union<Shape>(
keyField: "kind",
serializers: kind => kind switch { Kind.Rect => Upcast(rectCodec), _ => throw new InvalidOperationException() },
keyFunc: shape => shape switch { Rect => Kind.Rect, _ => throw new InvalidOperationException() }
);
// Encode automatically adds the discriminator
var encodedShape = shapeCodec.Encode(JsonTranscoder.Instance, new Rect(3, 4));
var encodedShape = shapeCodec.Decode(JsonTranscoder.Instance, encShape);
Inline nested struct
StructCodec supports an "inline" key that allows embedding a nested struct without an extra object level. Use
StructCodec.Inline as the field name (Note: optional/default wrappers are handled when inlined)
public record Outer(int id, Inner inner)
{
public static readonly StructCodec<Outer> OuterCodec = StructCodec.For<Outer>(
.Field("id", Codecs.Int, o => o.id)
.Field(StructCodec.Inline, InnerCodec, o => o.inner)
.Build((id, inner) => new Outer(id, inner));
);
}
public record Inner(string name)
{
public static readonly StructCodec<Inner> InnerCodec = StructCodec.For<Inner>(
.Field("name", Codecs.String, i => i.name)
.Build(name => new Inner(name));
);
}
var example = new Outer(67, new Inner("funny"));
var encoded = Outer.Codec.Encode(JsonTranscoder.Instance, example);
This would be encoded as following:
{
"id": 67,
"name": "Funny"
}
Transcoders
- The examples use the JSON transcoder (
JsonTranscoder.Instance), but any transport can be supported by implementingITranscoder<T>. StructCodecencodes into the transcoder’s concept of a map/object and decodes from it using provided field definitions.- Optional and Default wrappers help you model absent fields and fallback values.
Versioned Struct Codecs
Versioned struct codecs (VersionedStructCodec<R>) tracks schema versions and automatically applies migrations:
private const string old_person_json = "{\"display_name\":\"Synesthesia Dev\", \"is_awesome\":true}";
public record Person(string Name, int Age, Optional<bool> IsAwesome)
{
public static readonly StructCodec<Person> CODEC = StructCodec.For<Person>()
.Field("name", Codecs.STRING, p => p.Name)
.Field("age", Codecs.INT, p => p.Age)
.Field("is_awesome", Codecs.BOOLEAN.Optional(), p => p.IsAwesome)
.Build((name, age, isAwesome) => new Person(name, age, isAwesome));
// schema version 1: added "age" field
// schema version 2: renamed "display_name" to just "name"
public static readonly VersionedStructCodec<Person> VERSIONED_CODEC = new VersionedStructCodec<Person>()
{
CurrentSchemaVersion = 2,
InnerCodec = Person.codec,
SchemaMigrationRegistry = SchemaMigrationRegistry.Builder()
// Specify for what transcoder type/format this migration is
.For<JsonElement>(migrations =>
{
// migration to version 1: ensure "age" exists
migrations.Add(1, (transcoder, _, output) => output.Put("age", transcoder.EncodeInt(0)));
// migration to version 2: copy "display_name" -> "name"
migrations.Add(2, (transcoder, input, output) =>
{
var name = transcoder.DecodeString(input.GetValue("display_name"));
output.Put("name", transcoder.EncodeString(name));
});
})
};
}
See the test suite under Codon.Tests for broader coverage (lists, maps, enums, unions, forward refs, array helpers,
etc.).
BinaryCodecs
BinaryCodecs work with a BinaryBuffer and have a similar API to normal codecs (Optional, Default, List, MapTo, Transform, Enum, Recursive, and composite Of helpers).
Quick roundtrip with primitives:
using Codon.Binary;
using Codon.Buffer;
var buf = Unpooled.Buffer();
// Write
BinaryCodec.Int.Write(buf, 42);
BinaryCodec.String.Write(buf, "hello");
BinaryCodec.Boolean.Write(buf, true);
// Read back in the same order
var int = BinaryCodec.Int.Read(buf); // 42
var string = BinaryCodec.String.Read(buf); // "hello"
var bool = BinaryCodec.Boolean.Read(buf); // true
Lists, maps, optionals, enums:
// List and Dictionary helpers
var listCodec = BinaryCodec.Int.List(); // IBinaryCodec<List<int>>
var mapCodec = BinaryCodec.String.MapTo(BinaryCodec.Int); // IBinaryCodec<Dictionary<string,int>>
var list = new List<int> { 1, 2, 3 };
listCodec.Write(buffer, list);
var listBack = listCodec.Read(buffer);
// Optional
var optInt = BinaryCodec.Int.Optional();
optInt.Write(buffer, Optional.Of(123));
var readOpt = optInt.Read(buffer); // present 123
// Enums
enum Color { Red, Green, Blue }
var colorCodec = BinaryCodec.Enum<Color>();
colorCodec.Write(buffer, Color.Green);
var color = colorCodec.Read(buffer); // Color.Green
Composite codecs (struct-like) using Of(...):
public record Person(string name, int age, bool active)
{
public static readonly IBinaryCodec<Person> Codec = BinaryCodec.For<Person>(
.Field(BinaryCodec.String, p => p.name)
.Field(BinaryCodec.Int, p => p.age)
.Field(BinaryCodec.Boolean, p => p.active)
.Build((name, age, active) => new Person(name, age, active));
}
Person.Codec.Write(buffer, new Person("Alice", 30, true));
var person = Person.Codec.Read(buffer);
Transform between types:
// Store a string as its length using the inner Int codec
var lengthAsString = BinaryCodec.Int.Transform(
from: (string s) => s.Length,
to: (int n) => new string('x', n)
);
lengthAsString.Write(buffer, "abcde");
var restored = lengthAsString.Read(buffer); //
Recursive structures are supported via BinaryCodec.Recursive(self => ...):
public record Node(string name, List<Node> children)
{
public static readonly IBinaryCodec<Node> Codec = BinaryCodec.Recursive<Node>(self =>
BinaryCodec.For<Node>(
.Field(BinaryCodec.String, n => n.name)
.Field(self.List(), n => n.children)
.Build((name, children) => new Node(name, children))
)
);
}
BinaryBuffer has helpers like ToArray()/FromArray and ReaderIndex/WriterIndex if you need more control over IO.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
- SynesthesiaDev.Codon.BinaryCodec (>= 2026.522.1)
- SynesthesiaDev.Codon.Codec (>= 2026.522.1)
- SynesthesiaUtils (>= 1.2.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on SynesthesiaDev.Codon.Codon:
| Package | Downloads |
|---|---|
|
Synesthesia.Common
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2026.522.1 | 24 | 5/22/2026 |
| 2026.522.0 | 28 | 5/22/2026 |
| 2026.520.1 | 38 | 5/20/2026 |
| 2026.520.0 | 33 | 5/20/2026 |
| 2026.426.0 | 96 | 4/26/2026 |
| 2026.425.0 | 92 | 4/25/2026 |
| 2026.330.1 | 108 | 3/30/2026 |
| 2026.330.0 | 101 | 3/30/2026 |
| 2026.220.1 | 105 | 2/20/2026 |
| 1.1.1 | 202 | 2/9/2026 |
| 1.1.0 | 114 | 2/9/2026 |
| 1.0.1 | 122 | 1/3/2026 |
| 1.0.0 | 137 | 12/12/2025 |