According to MDN documentation for JSON.stringify:
Properties of non-array objects are not guaranteed to be stringified in any particular order. Do
I am pretty sure this is because of the way different JavaScript engines keep track of object properties internally. Take this for example:
var obj = {
"1" : "test",
"0" : "test 2"
};
for(var key in obj) {
console.log(key);
}
This will log 1, 0 in e.g. Firefox, but 0, 1 in V8 (Chrome and NodeJS). So if you need to be deterministic, you will probably have to iterate through each key store it in an array, sort the array and then stringify each property separately by looping through that array.
JavaScript keys are intrinsically unordered. You have to write your own Stringifier to make this work, so I did.
Usage:
JSONc14n.stringify(obj)
Source:
var JSONc14n = {
stringify: function(obj){
var json_string,
keys,
key,
i;
switch(this.get_type(obj)){
case "[object Array]":
json_string = "[";
for(i = 0; i < obj.length; i++){
json_string += this.stringify(obj[i]);
if(i < obj.length - 1) json_string += ",";
}
json_string += "]";
break;
case "[object Object]":
json_string = "{";
keys = Object.keys(obj);
keys.sort();
for(i = 0; i < keys.length; i++){
json_string += '"' + keys[i] + '":' + this.stringify(obj[keys[i]]);
if(i < keys.length - 1) json_string += ",";
}
json_string += "}";
break;
case "[object Number]":
json_string = obj.toString();
break;
default:
json_string = '"' + obj.toString().replace(/["\\]/g,
function(_this){
return function(character){
return _this.escape_character.apply(_this, [character]);
};
}(this)
) + '"';
}
return json_string;
},
get_type: function(thing){
if(thing===null) return "[object Null]";
return Object.prototype.toString.call(thing);
},
escape_character: function(character){
return this.escape_characters[character];
},
escape_characters: {
'"': '\\"',
'\\': '\\\\'
}
};
You may want to try JSON.sortify, a little helper that I wrote.
In contrast to the answers given so far, it
undefined
values and functionstoJSON()
Some things you may want to consider: What does it mean for an object to be different? Are you looking to see if a property on on that object has changed? Who is interested in 'knowing' about these changes? Do you want to know immediately if an objects property has changed?
You could make the properties on that object 'observable' properties and when that property changes you can fire an event and whoever is interested can subscribe to those property changes. That way you know immediately what has changed and you can do whatever you want with that information. Knockout.js use this approach. This way you do not have to resort to 'nasty' object comparisons
Recently I've had a similar use case. Following code has no dependencies and works for all browsers:
function stringify(obj) {
var type = Object.prototype.toString.call(obj);
// IE8 <= 8 does not have array map
var map = Array.prototype.map || function map(callback) {
var ret = [];
for (var i = 0; i < this.length; i++) {
ret.push(callback(this[i]));
}
return ret;
};
if (type === '[object Object]') {
var pairs = [];
for (var k in obj) {
if (!obj.hasOwnProperty(k)) continue;
pairs.push([k, stringify(obj[k])]);
}
pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1 });
pairs = map.call(pairs, function(v) { return '"' + v[0] + '":' + v[1] });
return '{' + pairs + '}';
}
if (type === '[object Array]') {
return '[' + map.call(obj, function(v) { return stringify(v) }) + ']';
}
return JSON.stringify(obj);
};
stringify([{b: {z: 5, c: 2, a: {z: 1, b: 2}}, a: 1}, [1, 2, 3]])
'[{"a":1,"b":{"a":{"b":2,"z":1},"c":2,"z":5}},[1,2,3]]'
stringify([{a: 1, b:{z: 5, c: 2, a: {b: 2, z: 1}}}, [1, 2, 3]])
'[{"a":1,"b":{"a":{"b":2,"z":1},"c":2,"z":5}},[1,2,3]]'
Here's an implementation of a deterministic JSON.stringify() that I wrote (uses Underscore.js). It converts (non-array) objects recursively into sorted key-value pairs (as Arrays), then stringifies those. Original coderwall post here.
Stringify:
function stringify(obj) {
function flatten(obj) {
if (_.isObject(obj)) {
return _.sortBy(_.map(
_.pairs(obj),
function(p) { return [p[0], flatten(p[1])]; }
),
function(p) { return p[0]; }
);
}
return obj;
}
return JSON.stringify(flatten(obj));
}
Parse:
function parse(str) {
function inflate(obj, pairs) {
_.each(pairs, function(p) {
obj[p[0]] = _.isArray(p[1]) ?
inflate({}, p[1]) :
p[1];
});
return obj;
}
return inflate({}, JSON.parse(str));
}