Comment vérifier de manière déterministe qu'un objet JSON n'a pas été modifié?

Selon la documentation de MDN pour JSON.stringify :

Les propriétés des objets non-array ne sont pas garanties dans un ordre particulier. Ne comptez pas sur la commande de propriétés dans le même objet dans la chaîne.

J'avais espéré déterminer si un objet a changé en mettant en cache une version stringée de l'objet, puis en le comparant à une version ultérieurement restructurée de l'objet. Cela a semblé beaucoup plus simple que l'itinéraire récursif à travers l'objet et faire des comparaisons. Le problème est que, parce que la fonction JSON.stringify n'est pas déterministe, je pourrais techniquement obtenir une chaîne différente lorsque je stringifie le même objet.

Quelles autres options ai-je? Ou dois-je écrire une fonction de comparaison désagréable pour déterminer l'égalité des objets?

Je suis certain que cela est dû à la façon dont différents moteurs JavaScript surveillent les propriétés de l'objet en interne. Prenez ceci par exemple:

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

Cela enregistrera 1, 0 par exemple Firefox, mais 0, 1 en V8 (Chrome et NodeJS). Donc, si vous devez être déterministe, vous devrez probablement itérer à travers chaque magasin clé dans un tableau, trier le tableau et ensuite trier chaque propriété séparément en bouclant ce tableau.

Vous pouvez essayer JSON.sortify , un petit assistant que j'ai écrit.

Contrairement aux réponses données jusqu'ici, il

  • Travaille avec n'importe quel niveau de nidification
  • Peut gérer les touches numériques
  • Échappe aux caractères spéciaux des clés
  • Accepte le paramètre d' space ainsi que le petit paramètre de replacer utilisé
  • Jette un TypeError sur des références cycliques (comme il se doit)
  • Filtre des valeurs et des fonctions undefined
  • toJSON()

Voici une implémentation d'un JSON.stringify déterministe () que j'ai écrit (utilise Underscore.js ). Il convertit les objets (non-tableau) de manière récursive en paires de valeurs-clés classées (en tant que matrices), puis les califie. Message de la partition de code d'origine ici .

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

Ces jours-ci, j'ai joué avec la manière déterministe de stringiser un objet et j'ai écrit l'objet ordonné stringify à JSON, qui résout le dilemme noté ci-dessus: http://stamat.wordpress.com/javascript-object-ordered- Propriété-stringify /

Aussi, je jouais avec des implémentations de table hash personnalisées qui sont également liées au sujet: 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(','))+']'; } 

Utilisation de Underscore ou 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); }; 

Fonctionne dans Chrome et Firefox récents.

JSFiddle ici: http://jsfiddle.net/stchangg/ruC22/2/

Récemment, j'ai eu un cas d'utilisation similaire. Le code suivant n'a pas de dépendances et fonctionne pour tous les navigateurs:

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

Les clés JavaScript sont intrinsèquement non ordonnées. Vous devez écrire votre propre Stringifier pour que cela fonctionne, alors j'ai fait.

Usage:

 JSONc14n.stringify(obj) 

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

Certaines choses que vous voudrez peut-être considérer: qu'est-ce que cela signifie qu'un objet soit différent? Vous cherchez à voir si une propriété sur cet objet a changé? Qui s'intéresse à «connaître» ces changements? Voulez-vous savoir immédiatement si une propriété d'objets a changé?

Vous pouvez créer les propriétés sur ces propriétés «observables» de l'objet et lorsque cette propriété change, vous pouvez déclencher un événement et quiconque est intéressé peut vous abonner à ces changements de propriété. De cette façon, vous savez immédiatement ce qui a changé et vous pouvez faire ce que vous voulez avec cette information. Knockout.js utilise cette approche. De cette façon, vous ne devez pas recourir à des comparaisons d'objets «méchants»