JSON-DRY
Serialize objects while preserving references and custom class instances
Table of contents
Version 2.x!
First of all: Version 2.x of json-dry
is not able to parse output from version 1.x, if that output contains references!
The way references are made & revived has changed completely.
All other syntax has remained the same.
If you did not use json-dry
to store serialized objects long-term (so just on-the-fly, for communication) then it's probably safe to upgrade.
Installation
$ npm install json-dry
Usage
Basic example
This is a basic example of stringifying an object (containing multiple references to the same object) and parsing it again.
let Dry = require('json-dry');
let obj = {};
let ref = {
date : new Date(),
regex : /test/i
};
obj.reference_one = ref;
obj.reference_two = ref;
obj.date = ref.date;
let dried = Dry.stringify(obj);
let undried = Dry.parse(dried);
undried.reference_one == undried.reference_two;
undried.reference_one.date == undried.date;
Implementing methods for serializing & reviving instances
Let's create an example class you might want to serialize and revive:
function Person(options) {
this.firstname = options.firstname;
this.lastname = options.lastname;
}
Person.prototype.fullname = function fullname() {
return this.firstname + ' ' + this.lastname;
};
let jelle = new Person({firstname: 'Jelle', lastname: 'De Loecker'});
jelle.fullname();
So now we've created a very basic class, let's register the class and add the 2 required methods for serializing & reviving.
Dry.registerClass(Person);
Person.prototype.toDry = function toDry() {
return {
value: {
firstname : this.firstname,
lastname : this.lastname
}
};
};
Person.unDry = function unDry(value) {
var result = new Person(value);
return result;
};
Now let's try stringifying it:
let dried = Dry.stringify(jelle);
let undried = Dry.parse(dried);
undried.fullname();
Serializing & reviving instances with circular references
Some classes contain references to each other, for example:
let alpha = new Alpha(),
beta = new Beta();
alpha.beta = beta;
beta.alpha = alpha;
The problem is that when you serialize & then try to revive this, one of the unDry
methods will receive an un-revived placeholder. This can obviously cause issues, especially when setting the property has side-effects. So a new argument whenDone
has been added to the unDry
method, like so:
Alpha.prototype.unDry = function unDry(obj, custom_method, whenDone) {
let alpha = new Alpha();
whenDone(function() {
alpha.beta = obj.beta;
});
return alpha;
}
whenDone
functions will be called just before the Dry.undry()
function exits, so all the references will have been revived by then.
toObject
While Dry.stringify
will return you with a json-valid string, Dry.toObject
will give you a valid simplified object.
In fact: Dry.stringify
is just a function that performs JON.stringify
on Dry.toObject
's output.
Why would you want to use this? Things like Workers
and IndexedDB
communicate data using the structured clone algorithm. So instead of performing expensive stringify operations you can just use these objects.
Cloning objects & instances
JSON-Dry offers a specialized clone
method. While in theory you could clone an object by drying end reviving it, like so:
let cloned = Dry.parse(Dry.toObject(jelle))
This is a lot slower than using clone
, because toObject
needs to do extra work that can be ignored when cloning:
let cloned = Dry.clone(jelle);
Clone methods
If you've added a toDry
and unDry
method to your class, by default the clone
method will use those to create the clone.
However, you can also create another method that gets precedence:
dryClone
Person.prototype.dryClone = function dryClone(seen_map, custom_method) {
return new Person({
firstname : this.firstname,
lastname : this.lastname
});
}
Custom clone methods
The clone
method takes an extra parameter called custom_method
. If you're cloning something that has a function property with the same name, that'll be used.
This can be used when you want to redact certain parts, for example:
Person.prototype.specialOccasionClone = function specialOccasionClone(seen_map, custom_method) {
return new Person({
firstname : this.firstname[0] + '.',
lastname : this.lastname
});
};
let special_clone = Dry.clone(jelle, 'specialOccasionClone');
special_clone.fullname();
Project history
Earlier versions of the project were heavily based on circular-json, a small library that adds (circular) reference support to JSON.
A lot of the JavaScript code on my websites was already shared between the server & client side, but I also wanted an easy way of sending data to the client while retaining references & class instances, so I started adding features to circular-json and called it json-dry
(dry as in don't repeat yourself).
The versions of json-dry
before 2.0.0
used references to the path where the object was first seen, like ~paths~to~the~first~reference
. Unfortunately sometimes objects were nested so deep that these reference paths were a lot longer than the serialized version of the object itself.
That's why in this new version, objects that are used more than once are stored in the ~refs
array. This way all references to objects can be simple numbers, instead of paths.
Versioning
We use SemVer for versioning. For the versions available, see the tags on this repository.
License
This project is licensed under the MIT License - see the LICENSE file for details