Как детерминистически проверить, что объект JSON не был изменен?
Согласно документация MDN для JSON.stringify:
Свойства объектов без массива не гарантируются. в любом конкретном порядке. Не полагайтесь на упорядочение свойств внутри тот же объект в строковом выражении.
Я надеялся определить, изменился ли объект путем кэширования строковой версии объекта, а затем сравнил его с последующей строкой версии объекта. Это казалось намного проще, чем рекурсивно итерации через объект и выполнения сравнений. Проблема в том, что поскольку функция JSON.stringify не является детерминированной, я могу технически получить другую строку, когда я строю один и тот же объект.
Какие еще варианты у меня есть? Или мне нужно написать неприятную функцию сравнения для определения равенства объекта?
Ответы
Ответ 1
Я уверен, что это связано с тем, как разные JavaScript-движки отслеживают внутренние свойства объекта. Возьмите это, например:
var obj = {
"1" : "test",
"0" : "test 2"
};
for(var key in obj) {
console.log(key);
}
Это будет log 1, 0, например. Firefox, но 0, 1 в V8 (Chrome и NodeJS).
Поэтому, если вам нужно быть детерминированным, вам, вероятно, придется перебирать каждый ключ в хранилище в массиве, сортировать массив и затем строчить каждое свойство отдельно, перейдя через этот массив.
Ответ 2
Вы можете попробовать JSON.sortify, немного помощник, который я написал.
В отличие от ответов, представленных до сих пор,
- работает с любым уровнем вложенности
- может обрабатывать числовые клавиши
- выделяет специальные символы в клавишах
- принимает параметр
space
, а также мало используемый параметр replacer
- выбрасывает TypeError для циклических ссылок (как и должно)
- фильтры
undefined
значения и функции
- respects
toJSON()
Ответ 3
Здесь реализована реализация детерминированного JSON.stringify(), который я написал (использует Underscore.js). Он рекурсивно преобразует (не массивы) объекты в отсортированные пары ключ-значение (как массивы), а затем строит их. Оригинальная запись coderwall здесь.
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));
}
Анализировать:
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));
}
Ответ 4
В эти дни я играл с детерминированным способом подстроить объект, и я написал упорядоченный объект для 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(','))+']';
}
Ответ 5
Использование 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.
JSFiddle здесь: http://jsfiddle.net/stchangg/ruC22/2/
Ответ 6
Недавно у меня был аналогичный вариант использования.
Следующий код не имеет зависимостей и работает для всех браузеров:
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]]'
Ответ 7
Ключи 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: {
'"': '\\"',
'\\': '\\\\'
}
};
Ответ 8
Некоторые вещи, которые вы можете рассмотреть:
Что означает, что объект будет другим?
Вы хотите посмотреть, изменилось ли свойство на этом объекте?
Кто заинтересован в "знании" об этих изменениях?
Вы хотите немедленно узнать, изменилось ли свойство объекта?
Вы можете сделать свойства свойств этого объекта "наблюдаемыми", и когда это свойство изменится, вы можете запустить событие, и тот, кто заинтересован, может подписаться на эти изменения свойств. Таким образом вы сразу узнаете, что изменилось, и вы можете делать все, что хотите, с помощью этой информации. Knockout.js используют этот подход. Таким образом, вам не нужно прибегать к "неприятным" сопоставлениям объектов