Backends
An MCodec[T] never names a format. It reads from an abstract Input and writes to an abstract Output, a small streaming API. A backend implements that pair for one wire format, and every derived codec runs against it unchanged.
The Input / Output model
Output offers three things to write:
writeNull()writeSimple(): SimpleOutput, then one ofwriteString/writeInt/writeLong/writeDouble/writeBoolean/writeBigInt/writeBigDecimal(writeByte,writeChar,writeBinary, … sit on top of those)writeList(): ListOutputandwriteObject(): ObjectOutput, sequential writers you feed elements or named fields
Input mirrors it: readNull(), readSimple(), readList(), readObject(), skip(), and on backends that can peek, peekKind() — used by @flatten and @outOfOrder.
A product codec calls output.writeObject(), writes each field by name, and closes it. A collection codec calls writeList(). Reading walks ObjectInput / ListInput with hasNext, nextField(), and nextElement().
The JSON backend
Json is the only wired-up entry point. Json.write[T](value) and Json.read[T](string) each need a given MCodec[T]:
import halotukozak.mcodec.*
case class Point(x: Int, y: Int) derives MCodec
val s: String = Json.write(Point(1, 2)) // {"x":1,"y":2}
val p: Point = Json.read[Point](s)
Json.write builds the String through a JsonOutput. Json.read parses it with a JsonReader behind a JsonInput and checks that nothing is left over.
BSON and CBOR
Input / Output implementations for BSON and CBOR sit in the source tree and run in the test suite. There is no public Bson / Cbor read/write object yet, so they are not part of the supported API.
Writing your own backend
Implement Input and Output. The InputAndSimpleInput / OutputAndSimpleOutput helper traits fold the simple readers and writers in when your format doesn't split them out. Every existing MCodec[T] then works against your backend as is.