简体   繁体   中英

Compare Two JSON Objects to See Changes Angular

I need to refactor my program to take a JSON obj, store it somewhere, make a bunch of changes, then compare the two objects to see what has been changed, deleted, and added.

I'm not sure of a way to do this in JS, so could anyone advise a way to do this in Angular (the object comparison part)? Otherwise, I'm going to have to make a ton of changes to the way my program runs / try it from the backend. Appreciate any help.

The only built in operation for object comparison is the == / === equality operators, which use reference equality: A is B, rather than A is equal to B.

What you want is a list of change descriptors describing the difference between two objects.

As identified in the comments, this is going to need a recursive traversal of the objects, using a mixture of reference and existence checks.

The following algorithm is a quick implementation of an idea. The objects are traversed and their changes are described with a list of objects. Just like the objects themselves, the changes are nested, but have a unique id, based on their location within the object (so it could be flattened).

function diff(a, b, namespace) {
  namespace = (namespace || '') + '.';

  var keysInA = Object.keys(a),
      keysInB = Object.keys(b);

  var diffA = keysInA.reduce(function(changes, key) {
    var ns = namespace + key;

    if(typeof b[key] == 'undefined') {
      return changes.concat([{ type: 'DELETED', id: ns }]);
    }

    if(a[key] !== b[key]) {
      return changes.concat([{ type: 'CHANGED', id: ns }]);
    }

    if(typeof a[key] == b[key] == 'object') {
      return diff(a[key], b[key], ns);
    }

    return changes; 
  }, []);

  var diffB = keysInB.reduce(function(changes, key) {
    var ns = namespace + key;

    if(typeof a[key] == 'undefined') {
      return changes.concat([{ type: 'ADDED', id: ns }]);
    }

    return changes;
  }, []);

  return diffA.concat(diffB);
}

For example we take the original state of an object.

var a = { a: 1, b: 2, c: 3 };

And the new state.

var b = { a: 2, c: 3, d: 5 };

Then run them with the diff function.

diff(a, b);

It returns a list of the changes.

[
  { id: '.a', type: 'CHANGED' },
  { id: '.b', type: 'DELETED' },
  { id: '.d', type: 'ADDED' }
]

Obviously, you would have to adapt this algorithm to make it fit your criteria for what constitutes a change. You might want to look at deep equality , rather than comparing references the whole way down.

I'll add my implementation of Dan's suggestion here in case it will help someone who wants to see an actual implementation:

var propertiesToIgnore = ['.indexesTracked', '.notInSyncWithDb', '.date', '.details.updatedAt', '.autoLoadLocalStorage', '.deletionQueue']; //these are extraneous properties added to project that should not be represented in interface (and not tracked)
var keysToIgnore = ['addAllDatacuts', 'datacutNames']; // this just looks at the property rather than the above which matches from the project root

function diff(a, b, namespace, firstCall) {
    namespace = firstCall ? (namespace || '') : (namespace || '') + '.';

    var keysInA = Object.keys(a),
        keysInB = Object.keys(b);

    var diffA = keysInA.reduce(function(changes, key) {
      var ns = namespace + key;

      if (propertiesToIgnore.indexOf(ns) !== -1 || keysToIgnore.indexOf(key) !== -1) {
        return changes;
      }

      if (key == '$$hashKey') {
        return changes;
      }

      if (angular.equals(a[key], b[key])) { // whole chain is equal so I do not need to iterate over this branch
        return changes;
      }

      if (typeof b[key] == 'undefined') {
        return changes.concat([{ type: 'DELETED', id: ns }]);
      }

      if (a[key] !== b[key] && (typeof b[key] !== 'object' || typeof a[key] !== 'object')) {
        return changes.concat([{ type: 'UPDATED', id: ns }]);
      }

      if (typeof b[key] === 'object' && typeof a[key] === 'object') {
        return changes.concat(diff(a[key], b[key], ns));
      }
      if (a[key] === null || b[key] === null) { // avoids values that are null as js considers null an object
        return changes;
      }

      if(typeof a[key] == 'object' && typeof b[key] == 'object' && typeof a[key].getMonth !== 'function' && typeof b[key].getMonth !== 'function') { // last part necessary to make sure it is not a date object
        return diff(a[key], b[key], ns);
      }

      return changes;
    }, []);

    var diffB = keysInB.reduce(function(changes, key) {
      var ns = namespace + key;

      if (propertiesToIgnore.indexOf(ns) !== -1 || keysToIgnore.indexOf(key) !== -1) {
        return changes;
      }

      if (key == '$$hashKey') {
        return changes;
      }

      if (typeof a[key] == 'undefined') {
        return changes.concat([{ type: 'ADDED', id: ns }]);
      }

      return changes;
    }, []);

    return diffA.concat(diffB);
  }

  $scope.changes = diff(dbData, $scope.project, '');

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM