Skip to content

adz/CodecMapper

Repository files navigation

CodecMapper

CI NuGet Docs License

CodecMapper is a schema-first serialization library for F# with native AOT and Fable compatibility.

It lets you define one schema and compile it into multiple codecs. The same mapping drives both encode and decode, so JSON and XML stay symmetric.

It's for cases where the wire contract should be explicit, reviewable, and reusable instead of being inferred from CLR shape or serializer settings.

The idea

You author one Schema<'T> that describes the wire shape:

open CodecMapper
open CodecMapper.Schema

type Person = { Id: int; Name: string }
let makePerson id name = { Id = id; Name = name }

let personSchema =
    define<Person>
    |> construct makePerson
    |> field "id" _.Id
    |> field "name" _.Name
    |> build

Then you compile that schema into a reusable codec:

let codec = Json.compile personSchema

let person = { Id = 1; Name = "Ada" }
let json = Json.serialize codec person
let decoded = Json.deserialize codec json

That is the core model of the library:

  • the schema is the contract
  • encode and decode come from the same definition
  • contract changes stay visible in one place

That same authored path also covers explicit tagged unions, string-valued enums, message envelopes, and recursive case trees through Schema.union, Schema.inlineUnion, Schema.envelope, Schema.stringEnum, and Schema.delay.

Why use it

CodecMapper fits when:

  • you want the wire contract to be authored explicitly
  • JSON and XML should stay symmetric
  • domain refinement should be explicit with Schema.map or Schema.tryMap
  • Native AOT and Fable compatibility matter

It is not trying to replace convention-based serializers for every use case.

Formats and scope

The same authored schema can compile into:

  • JSON codecs
  • XML codecs
  • config-oriented YAML codecs
  • flat KeyValue projections

Authored tagged unions stay on that same contract path instead of switching to a separate codegen or reflection model.

The core library stays focused on explicit schemas and handwritten runtimes. The separate bridge assembly exists for .NET interoperability with existing C# serializer contracts.

Start here

Use these after the core authored path is clear:

Compatibility

CodecMapper is designed to stay usable from Native AOT and Fable-oriented targets. CI includes both in-repo compatibility sentinels and packaged-consumer Fable checks.

Performance Status

Current status is mixed but clear:

  • CodecMapper is competitive on small messages and medium nested-record workloads.
  • System.Text.Json still leads on string-heavy and numeric-heavy payloads.
  • Newtonsoft.Json trails both across the current manual scenario matrix.

The project ships both a manual scenario runner and a repeatable perf workflow for hot-path investigation:

Latest local manual snapshot, measured on March 16, 2026:

Scenario CodecMapper serialize STJ serialize CodecMapper deserialize STJ deserialize Takeaway
small-message 519.5 ns 676.9 ns 990.1 ns 928.4 ns CodecMapper still wins tiny-message serialize; STJ keeps a slight decode lead.
person-batch-25 8.83 us 8.36 us 26.08 us 20.41 us Medium nested serialize stays close, but decode is not yet even.
person-batch-250 86.93 us 78.18 us 247.16 us 190.27 us Larger nested batches remain competitive on serialize, while STJ leads decode throughput.
escaped-articles-20 46.00 us 33.87 us 80.78 us 63.08 us String-heavy payloads are still a clear weak spot.
telemetry-500 393.93 us 311.45 us 745.63 us 520.84 us Numeric-heavy flat payloads still need significant optimization work, especially on decode.
person-batch-25-unknown-fields 7.92 us 7.51 us 30.50 us 24.23 us Unknown-field decode improved, but STJ still holds a noticeable lead.

Those numbers are machine-specific. Compare ratios and workload shape more than the absolute values.