Подтвердить что ты не робот

Как детерминистически проверить, что объект JSON не был изменен?

Согласно документация MDN для JSON.stringify:

Свойства объектов без массива не гарантируются. в любом конкретном порядке. Не полагайтесь на упорядочение свойств внутри тот же объект в строковом выражении.

Я надеялся определить, изменился ли объект путем кэширования строковой версии объекта, а затем сравнил его с последующей строкой версии объекта. Это казалось намного проще, чем рекурсивно итерации через объект и выполнения сравнений. Проблема в том, что поскольку функция JSON.stringify не является детерминированной, я могу технически получить другую строку, когда я строю один и тот же объект.

Какие еще варианты у меня есть? Или мне нужно написать неприятную функцию сравнения для определения равенства объекта?

4b9b3361

Ответ 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 используют этот подход. Таким образом, вам не нужно прибегать к "неприятным" сопоставлениям объектов