serialization

Provides JSON serialisation and parsing for unions.

This feature is experimental!

This API is still experimental, so it may change or be removed in future versions. You should not rely on it for production applications.

Signature

serialization(variant, adt)
(Variant, ADT) => Void

Documentation

Provides JSON serialisation and parsing for unions.

The serialization derivation bestows .toJSON() and .fromJSON(value) upon unions constructed by adt/union. Both serialisation and parsing are recursive, and .fromJSON can automatically reify values of other types.

Example:

const { union, derivations } = require('folktale/adt/union');
const Id = union('Id', {
  Id(value){ return { value } }
}).derive(
  derivations.serialization,
  derivations.equality
);

Id.Id(1).toJSON();
// ==> { '@@type': 'Id', '@@tag': 'Id', '@@value': { value: 1 } }

Id.fromJSON(Id.Id(1).toJSON());
// ==> Id.Id(1)

JSON serialisation

This derivation provides JSON serialisation through the .toJSON method, which converts rich unions into objects that can be safely serialised as JSON. For example:

const { union, derivations } = require('folktale/adt/union');

const { Id } = union('Id', {
  Id(value){ return { value } }
}).derive(derivations.serialization);

Id(1).toJSON();
// ==> { '@@type': 'Id', '@@tag': 'Id', '@@value': { value: 1 } }

During the transformation, if any of the values contains a .toJSON method, that's called to serialise the structure. Otherwise the value is just returned as-is:

Id(Id(1)).toJSON();
// ==> { '@@type': 'Id', '@@tag': 'Id', '@@value': { value: { '@@type': 'Id', '@@tag': 'Id', '@@value': { 'value': 1 } } } }

It's not necessary to call the .toJSON() method directly in most cases, since JSON.stringify will already invoke that for you:

JSON.stringify(Id(1));
// ==> '{"@@type":"Id","@@tag":"Id","@@value":{"value":1}}'

JSON.stringify(Id([Id(1)]));
// ==> '{"@@type":"Id","@@tag":"Id","@@value":{"value":[{"@@type":"Id","@@tag":"Id","@@value":{"value":1}}]}}'

JSON parsing

The reverse process of serialisation is parsing, and the .fromJSON method provided by this derivation is able to reconstruct the proper union from serialised data:

const { union, derivations } = require('folktale/adt/union');

const Id = union('Id', {
  Id(value){ return { value } }
}).derive(
  derivations.serialization,
  derivations.equality
);

const json = Id.Id(1).toJSON();
Id.fromJSON(json);
// ==> Id.Id(1)

In general, as long as the values in an union are either union instances or simple values supported by JSON, the following equivalence holds:

Union.fromJSON(union.toJSON()) = union

Some unions instances may contain other union instances as values. Serialising them is simple because JavaScript's dispatch takes care of selecting the correct serialisation for us. With parsing we don't have that luck, so instead the union takes a list of parsers as argument:

const A = union('A', { 
  A(value) { return { value } }
}).derive(
  derivations.serialization,
  derivations.equality
);

const B = union('B', {
  B(value) { return { value } }
}).derive(
  derivations.serialization,
  derivations.equality
);

A.fromJSON(A.A(B.B(1)).toJSON(), [A, B]);
// ==> A.A(B.B(1))

The serialisation format

In order to support the serialisation and parsing of unions, this module uses a specific format that encodes that information in the serialised data. This way, .toJSON() produces values of this interface, and .fromJSON(value) expects values of this interface:

type JSONSerialisation = {
  "@@type":  String,
  "@@tag":   String,
  "@@value": Object Any
}

Properties

Source Code

Defined in source/adt/union/derivations/serialization.js at line 106, column 0
(variant, adt) => {
  const typeName = adt[typeSymbol];
  const tagName = variant.prototype[tagSymbol];

  /*~
   * stability: experimental
   * module: null
   * authors:
   *   - "@boris-marinov"
   * 
   * type: |
   *   type JSONSerialisation = {
   *     "@@type":  String,
   *     "@@tag":   String,
   *     "@@value": Object Any
   *   }
   * 
   *   Variant . () => JSONSerialisation
   */
  variant.prototype.toJSON = function() {
    return { 
      [typeJsonKey]:  typeName, 
      [tagJsonKey]:   tagName, 
      [valueJsonKey]: mapValues(this, serializeValue) 
    };
  };

  /*~
   * stability: experimental
   * module: null
   * authors:
   *   - "@boris-marinov"
   * 
   * type: |
   *   type JSONSerialisation = {
   *     "@@type":  String,
   *     "@@tag":   String,
   *     "@@value": Object Any
   *   }
   *   type JSONParser = {
   *     fromJSON: (JSONSerialisation, Array JSONParser) => Variant
   *   }
   * 
   *   (JSONSerialisation, Array JSONParser) => Variant
   */
  adt.fromJSON = function(value, parsers = { [typeName]: adt }, keysIndicateType = false) {
    const valueTypeName = value[typeJsonKey];
    const valueTagName = value[tagJsonKey];
    const valueContents = value[valueJsonKey];
    assertType(typeName, valueTypeName);
    const parsersByType = keysIndicateType ? parsers
          : /*otherwise*/                    indexByType(values(parsers));

    const parsedValue = mapValues(valueContents, parseValue(parsersByType));
    return extend(Object.create(adt[valueTagName].prototype), parsedValue);
  };
}
Stability
experimental
Licence
MIT
Module
folktale/adt/union/derivations/serialization
Authors
Copyright
(c) 2013-2017 Quildreen Motta, and CONTRIBUTORS
Authors
  • @boris-marinov
Maintainers
  • Quildreen Motta <queen@robotlolita.me> (http://robotlolita.me/)