@effectful/serialization
TypeScript icon, indicating that this package has built-in type declarations

1.4.9 • Public • Published

@effectful/serialization

Extensible, references aware, schemaless JavaScript values serialization.

Usage

Install from npm:

$ npm install --save @effectful/serialization

In code:

import * as S from "@effectful/serialization"

const json = S.stringify(value)

// ....

const value = S.parse(str)

Writing JavaScript values

Function S.write takes JS objects and returns it encoded as JSON.stringify serializable value. It can deal with multiple references to same values, including recursive references.

The root value must always be a plain object. Its properties values may have any type if there is its format description registered.

The library contains descriptors for Array/Set/Map/Symbol types and can be extended to support any other type if can be serialized.

Library's S.stringify function is a write followed by JSON.stringify.

Reading functions

The reverse direction is done by S.read function. It takes a serializable object returned by write and returns its original value.

Function S.parse is JSON.parse followed by S.read.

Extension

There is a descriptor structure used to specify how to write and read values.

When writing, the descriptor is looked up in [S.descriptorSymbol] value's property or in the library's internal map. The write routine is responsible to output a string for type's name in "#type" property of output object. It is used to lookup corresponding descriptor for reading.

The descriptor is registered using S.regDescriptor function.

The library will generate unique names based on the name property in descriptor if there are collisions. This means sending and receiving sides must call the register function in the same order if there are names collisions chances.

More information in API docs.

Opaque values

Some values cannot be serialized, e.g. Promise, Function, WeakMap etc. They may be registered as opaque values. This way if there is a reference to such opaque value in serialized value the library outputs the reference name rather than the value. This works if same (corresponding) objects registered with the same name on sending and receiving sides. Typically they are module's top-level variables with functions or classes declarations.

To register opaque values call S.regOpaqueObject or S.regOpaquePrim. With their first argument is the value, and the second argument is its name. The name may be omitted for S.regOpaqueObject the library will try to guess it. If it is a function declaration, it will use its name, for example.

The third argument specifies additional options (see DescriptorOpts type in the API).

By default the output will contain properties set or changed in object after S.regOpaqueObject call. This can be disabled by setting {props:false} in the third argument to disable any property output. Or {propsSnapshot:false} to disable storing the original values snapshot (thus all properties will be output). Reads mutate the registered objects.

Calling S.regOpaqueObject will set [S.descriptorSymbol] property in the object, while S.regOpaquePrim will store the value in a library's internal map.

Prototypes based inheritance

Use S.regConstructor to make objects with JS prototype inheritance serializable. It gets constructor function as its first argument and optional name as the second. The constructor function's name will be used if the name argument isn't specified.

It will register the constructor's prototype as an opaque object. This way if all the method are only referenced in prototype and all the data referenced in the object is serializable the object is serializable.

Functions serialization

The function's source code in JS is simple to write to a string (with toString method) and simple to read (using Function constructor), however, there are two significant issues.

De-serializing functions by calling new Function(...) is apparently very dangerous. Developers must be absolutely sure there is absolutely no data received from outside. And it is quite difficult to guarantee.

Even if the security isn't a concern for the project, this way of reading/writing function will lose all information about closure captured variables. It is not even possible to serialize a result of Function.prototype.bind. This way the function can only get state data either from global variables (obviously not an option) or using this reference if the function is some object's method.

If these limitations are too heavy, there is a closure conversion transform pass. It converts closures into objects with call method. This may solve all the limitations. There are no needs to store function's text. Some unique identifier, say SHA1 is enough. And since there are no closures after conversions object's translation will work well.

The transform is in Proof Of Concept state name, but will be moved into a separate library SOON.

DOM serialization

The package contains proof-of-concept DOM serialization. It is disabled by default. To enable:

import * as SDOM "@effectful/serialization/dom"
SDOM.track()

This monkey patches some DOM functions to get access to internal platform state which is not accessible by JavaScript otherwise.

It is very limited for now, requires deeper wrapping. It will store/restore elements, events and document with all children, attributes, and properties of DOM values with some limitations. Events will keep only their initialization options with calculated properties (such as target) not initialized.

TODO:

  • Validation
  • Data versioning

LICENSE

Distributed under the terms of The MIT License (MIT).

Package Sidebar

Install

npm i @effectful/serialization

Weekly Downloads

50

Version

1.4.9

License

MIT

Unpacked Size

216 kB

Total Files

10

Last publish

Collaborators

  • effectful