How can I get a list of the differences between two JavaScript object graphs?

前端 未结 10 473
终归单人心
终归单人心 2020-11-29 01:02

I want to be able to get a list of all differences between two JavaScript object graphs, with the property names and values where the deltas occur.

For what it is w

相关标签:
10条回答
  • 2020-11-29 01:20

    This script has a NPM version as well, if you are using NodeJS. https://github.com/NV/objectDiff.js

    Rejoice.

    0 讨论(0)
  • 2020-11-29 01:26

    Here is a partial, naïve solution to my problem - I will update this as I further develop it.

    function findDifferences(objectA, objectB) {
       var propertyChanges = [];
       var objectGraphPath = ["this"];
       (function(a, b) {
          if(a.constructor == Array) {
             // BIG assumptions here: That both arrays are same length, that
             // the members of those arrays are _essentially_ the same, and 
             // that those array members are in the same order...
             for(var i = 0; i < a.length; i++) {
                objectGraphPath.push("[" + i.toString() + "]");
                arguments.callee(a[i], b[i]);
                objectGraphPath.pop();
             }
          } else if(a.constructor == Object || (a.constructor != Number && 
                    a.constructor != String && a.constructor != Date && 
                    a.constructor != RegExp && a.constructor != Function &&
                    a.constructor != Boolean)) {
             // we can safely assume that the objects have the 
             // same property lists, else why compare them?
             for(var property in a) {
                objectGraphPath.push(("." + property));
                if(a[property].constructor != Function) {
                   arguments.callee(a[property], b[property]);
                }
                objectGraphPath.pop();
             }
          } else if(a.constructor != Function) { // filter out functions
             if(a != b) {
                propertyChanges.push({ "Property": objectGraphPath.join(""), "ObjectA": a, "ObjectB": b });
             }
          }
       })(objectA, objectB);
       return propertyChanges;
    }
    

    And here is a sample of how it would be used and the data it would provide (please excuse the long example, but I want to use something relatively non-trivial):

    var person1 = { 
       FirstName : "John", 
       LastName : "Doh", 
       Age : 30, 
       EMailAddresses : [
          "john.doe@gmail.com", 
          "jd@initials.com"
       ], 
       Children : [ 
          { 
             FirstName : "Sara", 
             LastName : "Doe", 
             Age : 2 
          }, { 
             FirstName : "Beth", 
             LastName : "Doe", 
             Age : 5 
          } 
       ] 
    };
    
    var person2 = { 
       FirstName : "John", 
       LastName : "Doe", 
       Age : 33, 
       EMailAddresses : [
          "john.doe@gmail.com", 
          "jdoe@hotmail.com"
       ], 
       Children : [ 
          { 
             FirstName : "Sara", 
             LastName : "Doe", 
             Age : 3 
          }, { 
             FirstName : "Bethany", 
             LastName : "Doe", 
             Age : 5 
          } 
       ] 
    };
    
    var differences = findDifferences(person1, person2);
    

    At this point, here is what the differences array would look like if you serialized it to JSON:

    [
       {
          "Property":"this.LastName", 
          "ObjectA":"Doh", 
          "ObjectB":"Doe"
       }, {
          "Property":"this.Age", 
          "ObjectA":30, 
          "ObjectB":33
       }, {
          "Property":"this.EMailAddresses[1]", 
          "ObjectA":"jd@initials.com", 
          "ObjectB":"jdoe@hotmail.com"
       }, {
          "Property":"this.Children[0].Age", 
          "ObjectA":2, 
          "ObjectB":3
       }, {
          "Property":"this.Children[1].FirstName", 
          "ObjectA":"Beth", 
          "ObjectB":"Bethany"
       }
    ]
    

    The this in the Property value refers to the root of the object that was compared. So, this solution is not yet exactly what I need, but it is pretty darn close.

    Hope this is useful to someone out there, and if you have any suggestions for improvement, I am all-ears; I wrote this very late last night (i.e. early this morning) and there may be things I am completely overlooking.

    Thanks.

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

    Solution 1

    Use JSON.stringify(obj) to get a string representation of the objects you want to compare. Save the string to a file. Use any diff viewer to compare the text files.

    Note: JSON.stringify will ignore properties that point to function definitions.

    Solution 2

    This could do what you want with some modification, it is a modified version of the function _.isEqual (http://documentcloud.github.com/underscore/). Please feel free to suggest any modifications! I wrote it to figure out where the first difference between two objects occur.

    // Given two objects find the first key or value not matching, algorithm is a
    // inspired by of _.isEqual.
    function diffObjects(a, b) {
      console.info("---> diffObjects", {"a": a, "b": b});
      // Check object identity.
      if (a === b) return true;
      // Different types?
      var atype = typeof(a), btype = typeof(b);
      if (atype != btype) {
        console.info("Type mismatch:", {"a": a, "b": b});
        return false;
      };
      // Basic equality test (watch out for coercions).
      if (a == b) return true;
      // One is falsy and the other truthy.
      if ((!a && b) || (a && !b)) {
        console.info("One is falsy and the other truthy:", {"a": a, "b": b});
        return false;
      }
      // Unwrap any wrapped objects.
      if (a._chain) a = a._wrapped;
      if (b._chain) b = b._wrapped;
      // One of them implements an isEqual()?
      if (a.isEqual) return a.isEqual(b);
      // Check dates' integer values.
      if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
      // Both are NaN?
      if (_.isNaN(a) && _.isNaN(b)) {
        console.info("Both are NaN?:", {"a": a, "b": b});
        return false;
      }
      // Compare regular expressions.
      if (_.isRegExp(a) && _.isRegExp(b))
        return a.source     === b.source &&
               a.global     === b.global &&
               a.ignoreCase === b.ignoreCase &&
               a.multiline  === b.multiline;
      // If a is not an object by this point, we can't handle it.
      if (atype !== 'object') {
        console.info("a is not an object:", {"a": a});
        return false;
      }
      // Check for different array lengths before comparing contents.
      if (a.length && (a.length !== b.length)) {
        console.info("Arrays are of different length:", {"a": a, "b": b});
        return false;
      }
      // Nothing else worked, deep compare the contents.
      var aKeys = _.keys(a), bKeys = _.keys(b);
      // Different object sizes?
      if (aKeys.length != bKeys.length) {
        console.info("Different object sizes:", {"a": a, "b": b});
        return false;
      }
      // Recursive comparison of contents.
      for (var key in a) if (!(key in b) || !diffObjects(a[key], b[key])) return false;
      return true;
    };
    
    0 讨论(0)
  • 2020-11-29 01:32

    You can also try rus-diff https://github.com/mirek/node-rus-diff which generates MongoDB compatible (rename/unset/set) diff.

    For your example objects:

    var person1 = {
      FirstName: "John",
      LastName: "Doh",
      Age: 30,
      EMailAddresses: ["john.doe@gmail.com", "jd@initials.com"],
      Children: [
        {
          FirstName: "Sara",
          LastName: "Doe",
          Age: 2
        }, {
          FirstName: "Beth",
          LastName: "Doe",
          Age: 5
        }
      ]
    };
    
    var person2 = {
      FirstName: "John",
      LastName: "Doe",
      Age: 33,
      EMailAddresses: ["john.doe@gmail.com", "jdoe@hotmail.com"],
      Children: [
        {
          FirstName: "Sara",
          LastName: "Doe",
          Age: 3
        }, {
          FirstName: "Bethany",
          LastName: "Doe",
          Age: 5
        }
      ]
    };
    
    var rusDiff = require('rus-diff').rusDiff
    
    console.log(rusDiff(person1, person2))
    

    It generates a list of sets:

    { '$set': 
       { 'Age': 33,
         'Children.0.Age': 3,
         'Children.1.FirstName': 'Bethany',
         'EMailAddresses.1': 'jdoe@hotmail.com',
         'LastName': 'Doe' } }
    
    0 讨论(0)
提交回复
热议问题