How to deterministically verify that a JSON object hasn't been modified?

前端 未结 8 1702
忘了有多久
忘了有多久 2020-11-30 04:38

According to MDN documentation for JSON.stringify:

Properties of non-array objects are not guaranteed to be stringified in any particular order. Do

相关标签:
8条回答
  • 2020-11-30 05:00

    I am pretty sure this is because of the way different JavaScript engines keep track of object properties internally. Take this for example:

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

    This will log 1, 0 in e.g. Firefox, but 0, 1 in V8 (Chrome and NodeJS). So if you need to be deterministic, you will probably have to iterate through each key store it in an array, sort the array and then stringify each property separately by looping through that array.

    0 讨论(0)
  • 2020-11-30 05:01

    JavaScript keys are intrinsically unordered. You have to write your own Stringifier to make this work, so I did.

    Usage:

    JSONc14n.stringify(obj)
    

    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: {
            '"': '\\"',
            '\\': '\\\\'
        }
    };
    
    0 讨论(0)
  • 2020-11-30 05:06

    You may want to try JSON.sortify, a little helper that I wrote.

    In contrast to the answers given so far, it

    • works with any level of nesting
    • can handle numeric keys
    • escapes special characters in keys
    • accepts the space parameter as well as the little used replacer parameter
    • throws a TypeError on cyclical references (as it should)
    • filters undefined values and functions
    • respects toJSON()
    0 讨论(0)
  • 2020-11-30 05:06

    Some things you may want to consider: What does it mean for an object to be different? Are you looking to see if a property on on that object has changed? Who is interested in 'knowing' about these changes? Do you want to know immediately if an objects property has changed?

    You could make the properties on that object 'observable' properties and when that property changes you can fire an event and whoever is interested can subscribe to those property changes. That way you know immediately what has changed and you can do whatever you want with that information. Knockout.js use this approach. This way you do not have to resort to 'nasty' object comparisons

    0 讨论(0)
  • 2020-11-30 05:07

    Recently I've had a similar use case. Following code has no dependencies and works for all browsers:

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

    0 讨论(0)
  • 2020-11-30 05:13

    Here's an implementation of a deterministic JSON.stringify() that I wrote (uses Underscore.js). It converts (non-array) objects recursively into sorted key-value pairs (as Arrays), then stringifies those. Original coderwall post here.

    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));
    }
    
    0 讨论(0)
提交回复
热议问题