如何确定性地验证JSON对象是否未被修改?

时间:2012-01-19 19:20:12

标签: javascript json

根据MDN documentation for JSON.stringify

  

不保证非数组对象的属性是字符串化的   以任何特定的顺序。不要依赖于内部的物业订购   字符串化中的同一个对象。

我曾希望通过缓存对象的字符串化版本来确定对象是否已更改,然后将其与随后的字符串化对象版本进行比较。这似乎比递归迭代对象并进行比较要简单得多。问题是因为JSON.stringify函数不是确定性的,所以当我对同一个对象进行字符串化时,我可以在技术上得到一个不同的字符串。

我还有其他选择吗?或者我是否必须编写一个令人讨厌的比较函数来确定对象的相等性?

8 个答案:

答案 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,这是我写的一个小帮手。

与目前给出的答案相反,它

答案 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。

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

答案 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使用这种方法。这样您就不必诉诸'讨厌的'对象比较