如何确定性地验证JSON对象没有被修改?

wz3gfoph  于 2023-01-14  发布在  其他
关注(0)|答案(9)|浏览(142)

根据MDN documentation for JSON.stringify
非数组对象的属性不能保证以任何特定的顺序进行字符串化。在字符串化中,不要依赖于同一对象内属性的排序。
我曾希望通过缓存对象的字符串化版本,然后将其与对象的后续字符串化版本进行比较来确定对象是否发生了变化。这似乎比递归迭代对象并进行比较要简单得多。问题是,由于JSON. stringify函数是不确定的,因此在技术上,当我字符串化同一对象时,可能会得到不同的字符串。
我还有什么其他的选择?或者我必须写一个讨厌的比较函数来确定对象是否相等?

wlsrxk51

wlsrxk511#

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

var obj = {
"1" : "test",
"0" : "test 2"
};

for(var key in obj) {
    console.log(key);
}

这将记录1,0在例如Firefox,但0,1在V8(Chrome和NodeJS).所以,如果你需要确定性,你可能要迭代通过每个关键字存储在一个数组,排序数组,然后字符串化每个属性分别通过循环通过该数组.

00jrzges

00jrzges2#

你可能想试试JSON.sortify,我写的一个小助手。
与迄今为止给出的答案相比,

  • 适用于任何层次的嵌套
  • 可以处理数字键
  • 转义键中的特殊字符
  • 接受space参数以及很少使用的replacer参数
  • 在循环引用上抛出TypeError(应该如此)
  • 过滤器undefined值和函数
  • 尊重toJSON()
bf1o4zei

bf1o4zei3#

下面是我编写的一个确定性JSON.stringify()的实现(使用Underscore.js)。它递归地将(非数组)对象转换为排序的键值对(作为数组),然后将它们字符串化。
字符串化:

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

mec1mxoz4#

这些天来,我一直在尝试使用确定性方法来字符串化对象,我已经将有序对象字符串化写入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(','))+']';
}
ndasle7k

ndasle7k5#

使用下划线或虚线:

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/

relj7zay

relj7zay6#

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

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

vhipe2zx

vhipe2zx7#

JavaScript键本质上是无序的。你必须写你自己的字符串才能使这个工作,所以我做了。
用法:

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

cuxqih218#

您可能需要考虑的一些事项:对象不同意味着什么?您是否希望查看该对象上的属性是否已更改?谁对“了解”这些更改感兴趣?您是否希望立即了解对象属性是否已更改?
你可以让对象的属性成为“可观察的”属性,当属性改变时,你可以触发一个事件,任何感兴趣的人都可以订阅这些属性的改变。这样你就可以立即知道什么改变了,你可以对这些信息做任何你想做的事情。Knockout.js使用这种方法。这样你就不必求助于“讨厌的”对象比较

ghhaqwfi

ghhaqwfi9#

已接受的答案不再正确。
所有浏览器现在都以相同的顺序对键进行排序:

JSON.stringify({"b": "b", "01": "01", 01: 1, "0": 0, "a": "a"})
// {"0":0,"1":1,"b":"b","01":"01","a":"a"}"

Object.getOwnPropertyNames({"b": "b", "01": "01", 01: 1, "0": 0, "a": "a"})
// [ "0", "1", "b", "01", "a" ]
  • 对数值字段进行排序
  • 字符串字段不排序
  • 数值字段始终位于字符串字段之前
  • 指定数值字段时不需要引号,可以执行{ 1: "anything" }
  • 指定数字字段时有一个例外:
  • { 01: "anything" }将生成数值字段1
  • { "01": "anything" }将生成字符串字段"01"

在执行JSON.stringifyObject.getOwnPropertyNamesfor (var key in obj)时应用相同的排序。
确认日期:Safari浏览器、Chrome浏览器、火狐浏览器、Edge浏览器、IE11。
这意味着您可以使用JSON.stringify检查对象是否被修改。
只有一个条件:你不能使用delete操作来删除字符串字段,因为如果它们被再次添加,它们将被添加到字段列表的末尾。
您还可以使用JSON.stringify来比较两个不同的对象,只要您知道它们的字段是以相同的顺序创建的。
我必须补充的是,即使所有的JS引擎目前都以相同的方式处理对象,也不能保证将来会是这样,所以最好还是使用一个特殊的工具来进行比较。
我建议使用带有strict: true选项的deep-equal模块。

相关问题