JavaScript deterministic object stringify

For some time I needed to use objects as a hash table (associative array) key in JavaScript, and for all that time I skillfully evaded to wrap my head around it. JavaScript objects (hash tables/associative arrays) are unordered data sets which means that the property (key) order isn’t guaranteed. I believed that for(var i in obj) will always execute the same way and that it will be the order properties were added into the object. Also I knew that JSON.stringify() uses the same way of iteration. I tinkered with hash tables last two days and made my implementation in JavaScript https://stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/, and it used a function I’ve created that orderes the properties by creating a new object and inserting the properties in the correct order. It an array with keys, sorts them alphabetically or via provided function which is not obligatory, and then it fills the empty object with properties in new order. Now when I think of it, I forgot to cover a case where a property holds an array and in that array there is an object. So I fixed that too. Helper functions in both examples described in https://stamat.wordpress.com/2013/04/09/javascript-data-type-detection/

But snooping around online, and thinking about hash tables while I was in the bathroom, I thought to myself, that if key names are unimportant for a hash map, the list of them has to be stored somewhere for ‘for(var i in obj)’ iteration. But what if the list while stored in memory can change the order of it’s items to better suite memory needs, and I bet it isn’t invoked until ‘for(var i in obj)’, cause there is no need. I also read on MDN about JSON.stringify(): “Properties of non-array objects are not guaranteed to be stringified in any particular order. Do not rely on ordering of properties within the same object within the stringification.”, and about half an hour ago I started thinking, why not upgrade the previous function to do stringification, I have all the recursion there to create a deterministic JSON stringify, which means I will always know in what order properties are.

I saw a lot of people trying to find a solution for it, so here you go. JSON ordered objet stringification. Provide the object, and a sorting function. If you don’t provide the sorting function, properties will be sorted alphabetically in ascending order. By completing it I increased the performance of my HashCache class, cause now I don’t need to do two iterations, one for sorting and other for stringify.

Usage example:

//EXAMPLE
var example = {'a':1,
 'b':{'c':[1,2,[3,45],{'b':1, 'a':3},5],
 'd':{'q':function(){alert('foo')}, 'b':{'q':1, 'b':8},'c':4},
 'u':'lol'},
 'e':2};

alert(orderedStringify(example)); // {"a":1,"b":{"c":[1,2,[3,45],{"a":3,"b":1},5],"d":{"b":{"b":8,"q":1},"c":4},"u":"lol"},"e":2}
alert(JSON.stringify(sortProperties(example))); // {"a":1,"b":{"c":[1,2,[3,45],{"a":3,"b":1},5],"d":{"b":{"b":8,"q":1},"c":4},"u":"lol"},"e":2}

Live demo: http://jsfiddle.net/stamat/VL4DU/4/


//SORT WITH STRINGIFICATION
sortProperties = function(o, fn) {
var res = {};
var props = keys(o);
props = fn ? props.sort(fn): props.sort();
for(var i = 0; i < props.length; i++) {
res[props[i]] = o[props[i]];
}
return res;
};
var orderedStrigify = function(o, fn) {
var val = o;
var type = types[whatis(o)];
if(type === 3) {
val = _objectOrderedStrignify(o, fn);
} else if(type === 2) {
val = _arrayOrderedStringify(o, fn);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
return val;
};
var _objectOrderedStrignify = function(o, fn) {
var res = '{';
var props = keys(o);
props = fn ? props.sort(fn) : props.sort();
for(var i = 0; i < props.length; i++) {
var val = orderedStrigify(o[props[i]], fn);
if(val !== undefined)
res += '"'+props[i]+'":'+ val+',';
}
var lid = res.lastIndexOf(',');
if (lid > -1)
res = res.substring(res, lid);
return res+'}';
};
var _arrayOrderedStringify = function(a, fn) {
var res = '[';
for(var i = 0; i < a.length; i++) {
var val = orderedStrigify(a[i], fn);
if(val !== undefined)
res += ''+ val+',';
}
var lid = res.lastIndexOf(',');
if (lid > -1)
res = res.substring(res, lid);
return res+']';
};
//SORT WITHOUT STRINGIFICATION
var deepSortProperties = function(o, fn) {
var res = o;
var type = types[whatis(o)];
if(type === 3) {
res = _objectSortProperties(o, fn);
} else if(type === 2) {
res = _arraySortProperties(o, fn);
}
return res;
};
var _objectSortProperties = function(o, fn) {
var props = keys(o);
props = fn ? props.sort(fn) : props.sort();
var res = {};
for(var i = 0; i < props.length; i++) {
res[props[i]] = deepSortProperties(o[props[i]]);
}
return res;
};
var _arraySortProperties = function(a, fn) {
var res = [];
for(var i = 0; i < a.length; i++) {
res[i] = deepSortProperties(a[i]);
}
return res;
};
//HELPER FUNCTIONS
var keys = function(o) {
if(Object.keys)
return Object.keys(o);
var res = [];
for (var i in o) {
res.push(i);
}
return res;
};
var types = {
'integer': 0,
'float': 0,
'string': 1,
'array': 2,
'object': 3,
'function': 4,
'regexp': 5,
'date': 6,
'null': 7,
'undefined': 8,
'boolean': 9
}
var getClass = function(val) {
return Object.prototype.toString.call(val)
.match(/^\[object\s(.*)\]$/)[1];
};
var whatis = function(val) {
if (val === undefined)
return 'undefined';
if (val === null)
return 'null';
var type = typeof val;
if (type === 'object')
type = getClass(val).toLowerCase();
if (type === 'number') {
if (val.toString().indexOf('.') > 0)
return 'float';
else
return 'integer';
}
return type;
};

Sharing is caring! :)

4 comments

  1. I’d like to incorporate this into my request manager library https://github.com/onyxrev/apepi.js. What is the license for this code?

    1. Hey, the licence is “WTFPL” :D But thanks for notifying me and I’m glad the code is useful. :) An attribution is always cool but never obligatory! :D Your library looks very cool and very clean, and has an awesome name! I’ve written something similar myself, a year ago, but I still dint’t find time to publish it! Cheers mate!

  2. This is a nice code. Can you make it work with the topmost object being an array? Ex. [ [{“z”:10,”a”:”Hello”},2]

  3. […] other one is an upgrade to the first one, and it is a deterministic JSON stringify, that stringifies objects with alphabetically ordered properties: https://stamat.wordpress.com/2013/07/03/javascript-object-ordered-property-stringify/ […]

Leave a comment