-
Notifications
You must be signed in to change notification settings - Fork 72
Serialiser & Performance Benchmarks
JSON has limitations on the types of objects it will serialise and de-serialise back to an object. Two very good examples of this are the Date() and RegExp() objects. Both can be serialised via JSON.stringify() but when calling JSON.parse() on the serialised version neither type will be "re-materialised" back to their object representations.
For example here is the Date() serialisation:
var a = {
dt: new Date()
};
a.dt instanceof Date; // true
var b = JSON.stringify(a); // "{"dt":"2016-02-11T09:52:49.170Z"}"
var c = JSON.parse(b); // {dt: "2016-02-11T09:52:49.170Z"}
c.dt instanceof Date; // false
As you can see, parsing the JSON string works but the dt key no longer contains a Date instance and only holds the string representation of the date. This is a fundamental drawback of using JSON.stringify() and JSON.parse() in their native form.
In ForerunnerDB we need to serialise and de-serialise data in a lossless fashion because data types matter. To that end I have created a serialisation system in the Serialiser.js class that allows lossless serialisation / de-serialisation of common object types. The serialiser also provides extension capability to handle other custom types.
In the first incarnation of the Serialiser class I wrote the stringify and parse methods to traverse an object hierarchy, identify object instances that require special consideration and then ask their custom serialise / de-serialise methods to provide the correct data for the process.
In stringification of a Date() instance this provides output like this:
"{"dt": {"$date": "2016-02-10T09:52:49.170Z"}}"
When parsing this JSON string back to an object the whole string is first run through JSON.parse() and then traversed to identify the special object markers such as "$date". When one is encountered it is passed to the de-serialisation method provided for it and converted back to an object instance. The result is that the parsed data is the same as the original, object instances included.
My initial Serialiser class solved the problem but was around 5x slower than doing a baseline JSON.stringify() and JSON.parse() because of the extra processing required to identify special objects and handle them. While some performance hit will be required to process custom serialisation I wanted to try and speed up this critical process.
I decided to try and reduce the amount of processing the Serialiser had to do traversing the object tree by modifying the special object's .toJSON method. This meant that a basic JSON.stringify() would actually convert the object into the desired JSON string with the $date marker automatically.
You can see the resulting benchmark below:
Baseline is pure native JSON.stringify() and JSON.parse() without support for object instance encoding.
Current Implementation is the naive approach which traverses the object trees.
New Implementation is the new approach that modifies the toJSON() method on stringify and uses a reviver method passed to JSON.parse() on parse.
Serialisation of an object with lots of Date() instances is now as fast as a native JSON.stringify() call and produces JSON with $date markers to allow us to re-materialise object instances.
The de-serialisation method is around 1/3 faster than the initial implementation and around half the speed of the baseline native JSON.parse() which is acceptable given the important job it is doing :)