deserted

A simple and lonely serialization library for JS

Usage no npm install needed!

<script type="module">
  import deserted from 'https://cdn.skypack.dev/deserted';
</script>

README

deserted

A simple and lonely serialization library for JS

Installation

yarn add deserted

API Documenttation

Generated by Typedoc: https://vitoke.github.io/deserted/globals.html

Usage

Simple

import { deserted } from 'deserted'

const myObject = {
  value: 523,
  nested: {
    text: 'foo',
    bar: false
  },
  date: new Date(1990, 1, 1)
}

const asString = deserted.serialize(myObject)

const backToObject = deserted.deserialize(asString)

const quickCloneObject = deserted.clone(myObject)

Advanced

import { deserted, Converters } from 'deserted'

class Test {
  constructor() {
    this.foo = 1
    this.bar = true
  }
}
const test = new Test()
test.bar = false

const testSerializer = deserted.withConverters(Converters.allProps(Test))

const shared = { value: 123, { nested: test: 'foo' } }

const someObject = [shared, { value: shared }, new Map([1, shared])]

const asString = testSerializer.serialize(someObject)

const backToObject = testSerializer.deserialize(asString)

const quickCloneObject = testSerializer.clone(someObject)

Motivation

Serialization is important in any system dealing with data that should be saved or sent over a wire. It is also useful for deep cloning of objects. In a quick search for a simple to use but powerful library, I could not find anything that matches these requirements.

The deserted library can serialize complex object-graphs with circular references without configuration.

Things that are serializable out of the box:

  • primitives: number, string, boolean, undefined, null, Symbol
  • boxed primitives: Number, String, Boolean
  • objects
  • collections: Array, Map, Set
  • Date
  • Error

Things that require some configuration:

  • serializing custom class instances
  • serializing functions/code (limited, not the goal of this library)
  • serializing non-global Symbols, if they need to be reconnected to existing Symbols

Things that are not serializable:

  • Promises
  • any classes that have some hidden state

In-depth

(De)Normalization

The deserted library uses a two-phase approach to perform serialization. First, JS objects are 'normalized' to a JSON-like format. Basically, it creates a JSON object with object shapes based on primitive values that match the basic needed cases, like primitives, objects, class instances, references etc.

The normalize function on Deserted instances does exactly this.

For example, calling deserted.normalize({ test: 1 }) results in { obj: { test: { val: 1 } } }. This indicates that the serialized item is an object, is has a property called test, and the value of this property is the primitive value 1.

The resulting object can easily be fed to JSON.stringify. While this is also true for the source object, JSON.stringify easily breaks down when circular references are encountered.

Deserted also has the denormalize function that does exactly the inverse. Calling it on the result of the above example would return an object that is exactly the same as the provided object. But it is not the same in terms of references. Thus, it is a clone.

Cloning

Often it is necessary to copy or clone some input value, especially when dealing with functional frameworks. Redux is a common example for this, where state should be immutable, and thus the original object should not be modified. While except for small states, it is probably not a good idea to use this library for cloning, if cloning needs to be done occasionally, this library is a good fit.

I've seen examples of people using JSON.parse( JSON.stringify( someObject ) ) to do cloning. While that works, it has terrible performance, and does not handle circular references and also has no means to deal with custom classes.

Especially for this use case, Deserted provides the .clone method. All this does is perform denormalize( normalize ( someObject ) ), having the same effect but much faster. This is because there is no conversion to a String, and thus no parsing needed.

Configuration

Options

Deserted supports the following options, which can be supplied to Deserted.empty(options) Deserted.default(options) and .withOptions(options) on an existing instance:

  • noRefs: if true, the serializer will throw an exception if a circular reference is encountered.
  • functionConverter: a custom converter to serialize functions. By default they are replaced by a string.
  • symbolConverter: a custom converter to serialize Symbols. Useful if non-global symbols are used, and they need to be linked back.
  • stringifier: a custom converter that converts objects to strings and back. By default this is JSON.stringify/parse.

Custom converters

Deserted can also serialize custom class instances. However, you will need to configure how you want them to be serialized. The Converters object provides a number of common converters, but it's also possible to create your own.

Imagine we have the following class that we want to serialize:

class CounterState {
  constructor(initialCount = 0) {
    this.state = {
      count: initialCount,
      modifications: 0
    }
  }

  increase() {
    this.state.count++
    this.state.modifications++
  }

  decrease() {
    this.state.count--
    this.state.modifications++
  }
}

The class would only require its properties (the state property) to be serialized. Deserted can be configured to do that as follows:

import { deserted, Converters } from 'deserted'

const serializer = deserted.withConverters(Converters.allProps(CounterState))

const counter = new CounterState(10)
counter.increase()

const counterAsString = serializer.serialize(counter)

If you have a lot of custom classes that need the same type of converter, you can specify that as follows:

const serializer = deserted.withConverters(
  Converters.pick(SomeClass, 'prop1', 'prop2'),
  Converters.all(Converters.allProps, Class1, Class2, Class3)
)

Conclusion

The Deserted library is simple yet powerful, and allows to serialize complex object graphs with circular references with little effort. And it also makes deep cloning of objects a breeze.

Have fun!