SynesthesiaDev.Codon.Optionals
1.1.1
See the version list below for details.
dotnet add package SynesthesiaDev.Codon.Optionals --version 1.1.1
NuGet\Install-Package SynesthesiaDev.Codon.Optionals -Version 1.1.1
<PackageReference Include="SynesthesiaDev.Codon.Optionals" Version="1.1.1" />
<PackageVersion Include="SynesthesiaDev.Codon.Optionals" Version="1.1.1" />
<PackageReference Include="SynesthesiaDev.Codon.Optionals" />
paket add SynesthesiaDev.Codon.Optionals --version 1.1.1
#r "nuget: SynesthesiaDev.Codon.Optionals, 1.1.1"
#:package SynesthesiaDev.Codon.Optionals@1.1.1
#addin nuget:?package=SynesthesiaDev.Codon.Optionals&version=1.1.1
#tool nuget:?package=SynesthesiaDev.Codon.Optionals&version=1.1.1
🧬 Codon
Codon is a lightweight codec library for .NET
Codon has five packages:
Codon.BinaryBuffer- custom implementation of binary buffer, included inBinaryCodecpackageCodon.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
Lets 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, bool isAwesome)
{
public static readonly StructCodec<Person> Codec = StructCodec.Of
(
"name", Codecs.String, p => p.name,
"age", Codecs.Int, p => p.age,
"is_awesome", Codecs.Boolean, p => p.isAwesome,
(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.Of
(
"address", Codecs.String, p => p.address,
"height", Codecs.Int, p => p.height,
"weight", Codecs.Int, p => p.weight,
(address, height, weight) => new PersonalInformation(address, height, weight)
);
}
Now we can reference it in our Person class just like this:
"personal_information", PersonalInformation.Codec, p => p.personalInformation,
(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.Of(
"id", Codecs.String, u => u.id,
"display_name", Codecs.String.Optional(), u => u.displayName,
"level", Codecs.Int.Default(1), u => u.level,
(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.Of(
"w", Codecs.Int, r => r.w,
"h", Codecs.Int, r => r.h,
(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.Of(
"id", Codecs.Int, o => o.id,
StructCodec.Inline, InnerCodec, o => o.inner,
(id, inner) => new Outer(id, inner)
);
}
public record Inner(string name)
{
public static readonly StructCodec<Inner> InnerCodec = StructCodec.Of(
"name", Codecs.String, i => i.name,
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)
{
private static readonly StructCodec<Person> codec = StructCodec.Of (
"name", Codecs.String, p => p.Name,
"age", Codecs.Int, p => p.Age,
"is_awesome", Codecs.Boolean.Optional(), p => p.IsAwesome,
(name, age, someBoolean) => new Person(name, age, someBoolean)
);
// 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 = new BinaryBuffer();
// 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.Of(
BinaryCodec.String, p => p.name,
BinaryCodec.Int, p => p.age,
BinaryCodec.Boolean, p => p.active,
(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.Of(
BinaryCodec.String, n => n.name,
self.List(), n => n.children,
(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
- SynesthesiaUtils (>= 1.2.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on SynesthesiaDev.Codon.Optionals:
| Package | Downloads |
|---|---|
|
SynesthesiaDev.Codon.Codec
A .NET Codec library that can serialize into any format via custom transcoders |
|
|
SynesthesiaDev.Codon.BinaryCodec
A .NET Codec library that can serialize into binary format |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2026.220.5 | 42 | 2/20/2026 |
| 2026.220.1 | 32 | 2/20/2026 |
| 1.1.1 | 168 | 2/9/2026 |
| 1.1.0 | 148 | 2/9/2026 |
| 1.0.1 | 173 | 1/3/2026 |