OxL Reference

9.5 User-defined serialisers

Level 1 — representation through another type (Repr), acts in all formats and in schemas:

oxl
@repr(i64)
data Money { minor: i64 }

impl Repr<i64> for Money {
  fn toRepr() -> i64 => minor
  static fn fromRepr(v: i64) -> Money | DecodeError => Money{ minor: v }
}

Level 2 — a codec per type or field, including for a single format:

oxl
@explicit interface Codec<T> {
  static fn encode(x: &T, w: &var Encoder) -> () | EncodeError
  static fn decode(r: &var Decoder) -> T | DecodeError
  static fn schema() -> Schema               // for oxl export
}

data Event {
  @codec(json: Iso8601) at: Time            // in JSON an ISO 8601 string, in binary the default
  @codec(Base64Url) token: bytes             // in all formats
}

@codec(GeoHashCodec) data Point { lat: f64; lon: f64 }
  • @codec(C) — for all formats; @codec(json: C1, oxl: C2) — per format. C MUST implement Codec<T> for the field type.
  • Encoder/Decoder is the abstract interface of a format: writeI64, writeStr, beginMessage, field(tag, name), etc. (part II, std.ser).
  • Changing a field's codec is changing the field's type per table 9.2: the compiler compares the schema() of the old and the new codec.

Encoder and Decoder are interfaces of std.ser; written as a type (w: &var Encoder, -> Decoder) they mean dyn Encoder and dyn Decoder. In Encoder all methods have (empty) default implementations: beginMessage(name), endMessage(), field(tag, name), writeI64, writeU64, writeF64, writeBool, writeStr, writeBytes, writeNull, beginList(len), endList(), beginMap(len), endMap() and finish() -> bytes (the only way to return the accumulated bytes). Decoder contains the reading methods paired with the writing ones; the default implementations report DecodeError.Malformed.

Level 3 — a custom format. A type that implements the interface Format (name() -> str, encoder() -> Encoder, decoder(input: bytes) -> Decoder) is registered by the comptime call std.ser.register(MyFormat) and becomes available as encode<T>(x, MyFormat). The implementation is built on the Encoder/Decoder events and on reflect(T) (§15). The name Format in the argument position of encode/decode denotes both the enumeration of the built-in formats (Format.Json) and the interface Format for implementors; they are distinguished by position: impl Format for T implements the interface (D-8053; DIV-007; D-7620).

Implementation-defined. Decoding with a custom format (D-8053; DIV-007; D-7621). decode<T>(b, MyFormat) walks the type T and pulls the values from the Decoder in the order encode would have pushed them to the Encoder (D-8344): readI64/readU64/readF64/readBool/readStr/readBytes for scalars, beginMessage/nextField/endMessage for messages (a field is found by its tag, by its name when tag == 0), beginList/endList and beginMap/endMap for collections, readNull for T?. Limits of this implementation: the Decoder has no way to skip a value, so an unknown field of a message is an error (Invalid); an unknown number of an open enum is not read. Encoding and decoding with a custom format are supported.

schema-attr

See also

Source: docs/spec/oxl-standard.en.md