Serialization

Serialization

A value describes itself through Serialize and Deserialize without naming a format. A format implements Serializer and Deserializer once, and the two sides meet in the middle.

[public] trait Serialize {
    fn serialize(serializer: Serializer);
}

[public] trait Deserialize {
    [static] fn deserialize(deserializer: Deserializer) => Self?;
}

deserialize is [static], so it is called on the type rather than an instance, and it returns Self? because decoding can fail.

The format side:

[public] trait Serializer {
    fn begin_object();  fn end_object();  fn field(name: string);
    fn begin_array();   fn end_array();
    fn put_number(value: number);  fn put_string(value: string);
    fn put_bool(value: bool);      fn put_null();
}

[public] trait Deserializer {
    fn enter_field(name: string) => bool;   fn exit_field();
    fn length() => number?;
    fn enter_index(index: number) => bool;  fn exit_index();
    fn get_number() => number?;  fn get_string() => string?;
    fn get_bool() => bool?;      fn is_null() => bool;
    fn get_identifier() => string?;
}

Every read returns an optional: None means the value was absent or had the wrong shape.

number, bool, and string implement both traits already. char does not.

The [serial] derive

Writing both methods by hand is mechanical, so [serial] generates them:

[serial] class Settings {
    theme: string
    width: number
    tags: Array<string>
    nickname: string?
}

The generated code handles each field by its declared type:

  • An optional is written only when present, and on read starts at None and is filled in only if the field exists. A missing key is not an error.
  • An array writes a field, an array body, and each element; on read it requires the field, reads the length, and decodes each index.
  • Everything else, including nested [serial] classes and enums, delegates to that type's own serialize / deserialize. A missing required field produces an error carrying the field name.

Enums have no methods of their own, so the compiler routes them through generated helpers. A variant serializes as its identifier string, and an unknown string on read is an error.

With JSON

let text: string = json::stringify(settings)
let restored: Settings? = deserialize<Settings>(json::read(text))

json::stringify and json::serialize take any Serialize. json::read and json::reader produce a Deserializer. The deserialize<T>(source) built-in lowers to T::deserialize(source) and yields T?.

Worth knowing

This machinery is complete but is not yet exercised anywhere in the shipped toolkit: no shipped type uses [serial], and real code builds JSON by interpolation and escapes leaf strings with new json::JsonString(x).to_string(). Both approaches work; the derive is the better one for a type with more than a couple of fields.