如何确定性地validationJSON对象没有被修改?

根据JSON.stringify的MDN文档 :

非数组对象的属性不保证以任何特定的顺序被串行化。 不要依赖于string化中同一对象内的属性sorting。

我曾希望通过caching对象的string化版本来确定对象是否发生了更改,然后将其与对象的后续string化版本进行比较。 这似乎比迭代遍历对象和做比较简单得多。 问题是,因为JSON.stringify函数是不确定的,我可以在技术上得到一个不同的string,当我串化相同的对象。

我还有什么其他的select? 还是我必须写一个讨厌的比较函数来确定对象的相等性?

我很确定这是因为不同的JavaScript引擎在内部跟踪对象属性的方式。 以此为例:

 var obj = { "1" : "test", "0" : "test 2" }; for(var key in obj) { console.log(key); } 

这将在例如Firefox中logging1,0,而在V8中logging为0,1(Chrome和NodeJS)。 因此,如果您需要确定性,则可能需要遍历数组中的每个键存储,对数组进行sorting,然后通过遍历该数组分别对每个属性进行string化。

你可能想尝试JSON.sortify ,我写的一个小助手。

与迄今为止给出的答案相反,

  • 适用于任何级别的嵌套
  • 可以处理数字键
  • 转义密钥中的特殊字符
  • 接受space参数以及less量使用的replacer参数
  • 抛出一个TypeError的循环引用(因为它应该)
  • 过滤undefined值和函数
  • 尊重toJSON()

下面是我写的一个确定性JSON.stringify()的实现(使用Underscore.js)。 它将(非数组)对象recursion地转换为sorting的键值对(如数组),然后将这些对串联起来。 原始的coderwallpost在这里 。

string化:

 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)); } 

parsing:

 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)); } 

这些天,我一直在玩确定性的方式来对一个对象进行string化处理,并且把有序的对象stringify写入JSON,这就解决了上面提到的两难境地: http : //stamat.wordpress.com/javascript-object-ordered-财产string化/

另外我正在玩自定义哈希表实现这也是相关的主题: 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(','))+']'; } 

使用下划线或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。

JSF在这里: http : //jsfiddle.net/stchangg/ruC22/2/

最近我有一个类似的用例。 以下代码没有依赖关系,适用于所有浏览器:

 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]]'

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: { '"': '\\"', '\\': '\\\\' } }; 

有些事情你可能要考虑:一个对象是不同的意思是什么? 您是否正在查看该对象上的属性是否已更改? 谁有兴趣知道这些变化? 你想立即知道对象属性是否已经改变?

您可以在该对象的“可观察”属性上创build属性,当该属性发生更改时,您可以触发事件,任何感兴趣的人都可以订阅这些属性更改。 这样你就可以立即知道发生了什么变化,你可以用这些信息做任何你想做的事情。 Knockout.js使用这种方法。 这样你就不必诉诸“讨厌”的对象比较