Serialization Overview

Igor is designed to support multiple serialization formats.

Currently supported serialization formats are:

Serialization for each format is controlled by attributes in the respective domain.

Enabling Serialization Format

By default, only data types are generated, without any serialization code. To enable serialization, use _domain_.enabled attribute on data type or the whole module.

Example: enabling C# code generation for binary and JSON serialization for the whole module

[csharp binary.enabled json.enabled]
module Types
{
}

Excluding Fields

You can exclude a record field from serialization with _domain_.ignore attribute on the field:

[csharp binary.enabled json.enabled]
module Types
{
    record ExampleRecord
    {
        string always_serialized; // Both JSON and binary serialization are on
        [* binary.ignore]
        string json_only_comment;  // JSON serialization is on, but binary serialization is off
    }
}

Warning

To ensure that different languages can share the same format, make sure that record field is included or excluded for all targets.

Example:

// DO:
[* binary.ignore]
string value;

// DO NOT:
[csharp binary.ignore]
string value;

Custom Serialization

In some cases the default serialization code is not what user wants. There may be several reasons for that, for example:

  • Igor data is modelled for existing JSON or XML data layout, and this layout is not compatible with Igor serialization rules

  • Igor type is an alias for the existing target platform type, and Igor does not know enough details of that target type to be able to generate the correct code

In such cases the user is responsible for providing custom serialization methods via attributes. The set and meaning of custom serialization attributes differ for different target languages. See documentation for the specific target for details.

Patch Records

Sometimes it is useful to define records that are used to describe changes of a certain state.

Example:

record State
{
    int value;
    ?int opt_value;
}

record StateUpdate
{
    ?int value_update;
    ?int opt_value_update;
}

In the example above StateUpdate can be used to transfer State changes over network. We could use State directly, but it may be very large, so we introduce StateUpdate record where all fields are optional, and we can transfer only values that have been changed.

The problem with this example is that State.opt_value is optional, so its value range includes null value. So StateUpdate.opt_value_update valid range should add another null to this range, to represent absence of changes (unset value). But ??int type is not supported.

Patch records allow to distinguish between explicit null and unset values:

[* patch_record]
record StateUpdate
{
    int value_update;
    ?int opt_value_update;
}

All patch record fields are optional, so we don’t use optional type. If a value is of optional type (e.g. ?int), that means that its null value, if set, should be explicitly present in the serialized data. Target implementations should provide a way to distinguish between null and unset values (e.g. by using a special undefined value in TypeScript or Erlang).