Published on

RAMSON, part 1

Authors
test

RAMSON is a JSON schema that serializes and deserializes JS/ECMAScript objects precisely as they were in memory.

For example, consider the following Javascript code:

var state = { foo: 1 };
var root = { a: state, b: state };

root.a and root.b refer to the same object. To convince yourself experimentally, try:

root.a.foo = root.a.foo + 1;  // update root.a
console.log(root.b.foo);      // observe that root.b also changed

You'll notice that the output is 2.

However, if we were to use JSON to serialize and deserialize root, root.a and root.b would no longer be the same object: setting root.a.foo would no longer be reflected when reading root.b.foo.

A Naive Solution

A naive approach to solving this is to assign tags to each value as we encounter them.

First, let use define that:

IsPrimitive(t)={,if t{string,number,boolean,undefined,null},otherwise \text{IsPrimitive}(t) = \begin{cases}\top, &\text{if } t \in \{\text{string}, \text{number}, \text{boolean}, \text{undefined}, \text{null}\}\\\bot, & \text{otherwise}\end{cases},

Now, we can implement:

reftag(v)\text{reftag}(v) returns a monotonically incrementing number for every new vv,

Rserialize(v)={v,if IsPrimitive(T(v)){v,reftag:reftag(v)},otherwise R_{serialize}(v) = \begin{cases}v,& \text{if } \text{IsPrimitive}(T(v))\\\{\ldots v, \text{reftag}: \text{reftag}(v) \}, & \text{otherwise}\end{cases}

Specifically, for each value v, that's being serialized:

  • if v, is of type string, number, boolean, undefined, or null, serialize it as is,
  • if v, has no previously assigned reftag, assign one, and serialize the value including an extra reftag field 1,
  • if v, has a reftag, serialize only a reference to the reftag as reftag, eluding the actual value.

A reftag (reference tag) can be any string or numerical identifier that uniquely identifies an instance of an object. For example, one could use the actual memory address of the value in the JS interpreters memory, or just an incremental counter.

Example

For example, the root object from the aforementioned example could become:

{ "a": { "$$tag_def": 1, "foo": 1 }, "b": { "$$tag_ref": 1 } }

Recursive Objects

Consider that we also set root.self_ref = root. Whereas JSON.stringify would throw a "Converting circular structure to JSON" error, RAMSON would serialize it as

{ "$$def_reftag": 1, "a": { "$$def_reftag": 2, "foo": 1 }, "b": { "$$ref_reftag": 2 }, "self_ref": { "$$ref_reftag": 1 } }

Further work

This initial draft only covers strings, numbers, booleans, undefined, null, and objects. Given this, arrays are also trivial, but require fine-tuning the output schema. Functions, classes, and other exotic JS objects such instances of Promise, ArrayBuffer, and others will be explored in a future post.

Footnotes

  1. This assumes all values that are not string, number, boolean, undefined, or null are objects, but there are arrays too...