根据MDN documentation for JSON.stringify:
不保证非数组对象的属性是字符串化的 以任何特定的顺序。不要依赖于内部的物业订购 字符串化中的同一个对象。
我曾希望通过缓存对象的字符串化版本来确定对象是否已更改,然后将其与随后的字符串化对象版本进行比较。这似乎比递归迭代对象并进行比较要简单得多。问题是因为JSON.stringify函数不是确定性的,所以当我对同一个对象进行字符串化时,我可以在技术上得到一个不同的字符串。
我还有其他选择吗?或者我是否必须编写一个令人讨厌的比较函数来确定对象的相等性?
答案 0 :(得分:9)
我很确定这是因为不同的JavaScript引擎在内部跟踪对象属性的方式。以此为例:
var obj = {
"1" : "test",
"0" : "test 2"
};
for(var key in obj) {
console.log(key);
}
这将在例如1中记录0 Firefox,但在V8(Chrome和NodeJS)中为0,1。 因此,如果您需要确定性,您可能必须遍历数组中的每个密钥存储,对数组进行排序,然后通过循环遍历该数组来单独对每个属性进行字符串化。
答案 1 :(得分:9)
您可能想尝试JSON.sortify,这是我写的一个小帮手。
与目前给出的答案相反,它
space
parameter as well as the little used replacer
parameter undefined
值和功能toJSON()
答案 2 :(得分:5)
这是我写的确定性JSON.stringify()的实现(使用Underscore.js)。它以递归方式将(非数组)对象转换为已排序的键值对(如数组),然后对这些对象进行字符串化。原始的coderwall帖子here。
字符串化:
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));
}
解析:
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));
}
答案 3 :(得分:3)
这些天我一直在玩确定性的方法来对一个对象进行字符串化,并且我已经将有序对象stringify写成了JSON,这解决了上面提到的两难困境:http://stamat.wordpress.com/javascript-object-ordered-property-stringify/
此外,我正在玩自定义哈希表实现,这也与主题相关:http://stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/
//SORT WITH STRINGIFICATION
var orderedStringify = function(o, fn) {
var props = [];
var res = '{';
for(var i in o) {
props.push(i);
}
props = props.sort(fn);
for(var i = 0; i < props.length; i++) {
var val = o[props[i]];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val, fn);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += '"'+props[i]+'":'+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+'}';
};
//orderedStringify for array containing objects
var arrayStringify = function(a, fn) {
var res = '[';
for(var i = 0; i < a.length; i++) {
var val = a[i];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += ''+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+']';
}
答案 4 :(得分:3)
使用Underscore或Lodash:
var sortByKeys = function(obj) {
if (!_.isObject(obj)) {
return obj;
}
var sorted = {};
_.each(_.keys(obj).sort(), function(key) {
sorted[key] = sortByKeys(obj[key]);
});
return sorted;
};
var sortedStringify = function() {
arguments[0] = sortByKeys(arguments[0]);
return JSON.stringify.apply(this, arguments);
};
适用于最新的Chrome和Firefox。
答案 5 :(得分:1)
最近我有一个类似的用例。 以下代码没有依赖关系,适用于所有浏览器:
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]]'
答案 6 :(得分:1)
JavaScript密钥本质上是无序的。你必须编写自己的Stringifier才能使其工作,所以我做了。
用法:
JSONc14n.stringify(obj)
来源:
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: {
'"': '\\"',
'\\': '\\\\'
}
};
答案 7 :(得分:0)
您可能需要考虑以下事项: 对象与众不同意味着什么? 您是否想要查看该对象上的属性是否已更改? 谁对“了解”这些变化感兴趣? 如果对象属性已更改,您想立即知道吗?
您可以在该对象的'observable'属性上创建属性,当该属性更改时,您可以触发事件,并且任何感兴趣的人都可以订阅这些属性更改。这样你就可以立即知道发生了什么变化,你可以用这些信息做任何你想做的事情。 Knockout.js使用这种方法。这样您就不必诉诸'讨厌的'对象比较