How to compare two objects and get key-value pairs of their differences?
Asked Answered
I

7

14

I have two objects:

1)

{A: 10, B: 20, C: 30}

2)

{A: 10, B: 22, C: 30}

as you can see: there are almost equal, except one thing: key B value is different.

How can i get into my someNewArr key-value pare of differences?

like someNewArr: {B: 22} (i get values from second object)

i'm using angular, and i mean something like this:

    var compareTwoObjects = function(initialObj, editedObj) {
        var resultArr = [];
        angular.forEach(initialObj, function(firstObjEl, firstObjInd) {
            angular.forEach(editedObj, function(secondObjEl, secondObjInd) {
                if (firstObjEl.key === secondObjEl.key && firstObjEl.value !== secondObjEl.value){
                    resultArr.push({firstObjEl.key: secondObjEl.value});
                }
            })
        });
    });
Ivar answered 20/10, 2015 at 9:24 Comment(5)
What exactly is not working with the code you posted? Looks fine to me.Ultramontane
is there any possibility to having something {A: 10, B: 20, C: 30, D: 90}, {A: 10, B: 22, C: 30}?Alcalde
@K.Toress yes, it isIvar
Can you change your output to diff = {changed: {B:22}, removed:null, created:{}}. It will be great question, i thinkHiro
@SheraliTurdiyev yesIvar
U
34

recursive diff

Almost 3 years later, I'm happy to provide a refreshed answer to this question.

We start with two objects that are different

const x =
  { a: 1, b: 2, c: 3 }

const y =
  { a: 1, b: 3, d: 4 }

console.log (diff (x, y))
// => ???

Both objects have the same a property. The b property is not the same. Only x has a c property, and only y has a d property. So what should ??? be exactly?

From the perspective of diff, the relationship between our input objects a and b could be completely arbitrary. To communicate the which object contributes a difference, diff assigns descriptors left and right

console.log (diff (x, y))
// { b: { left: 2, right: 3 }, c: { left: 3 }, d: { right: 4 } }

In the output above we can see

  • which properties are different – b, c, and d
  • which object contributed the difference - left and/or right
  • the "different" value - for example the left b has a value of 2, the right b has a value of 3; or the left c has a value of 3, the right c has a value of undefined

Before we get into the implementation of this function, we'll first examine a more complex scenario involving deeply nested objects

const x =
  { a: { b: { c: 1, d: 2, e: 3 } } }

const y =
  { a: { b: { c: 1, d: 3, f: 4 } } }

console.log (diff (x, y))
// { a: { b: { d: { left: 2, right: 3 }, e: { left: 3 }, f: { right: 4 } } } }

As we can see above, diff returns a structure that matches our inputs. And finally we expect the diff of two objects that are the same to return an "empty" result

const x1 =
  { a: 1, b: { c: { d: 2 } } }

const x2 =
  { a: 1, b: { c: { d: 2 } } }

console.log (diff (x1, x2))
// {}

Above we describe a diff function that does not care about the input objects it is given. The "left" object can contain keys the "right" object does not contain, and vice versa, yet we still must detect changes from either side. Starting from a high-level, this is how we'll be approaching the problem

const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    ) 

diff1

We take a "one-sided" diff using diff1 described as the "left" relation, and we take another one-sided diff with the input objects reversed described as the "right" relation, then we merge the two results together

Our work is divided for us in tasks that are easier to accomplish now. diff1 only needs to detect half of the necessary changes and merge simply combines the results. We'll start with diff1

const empty =
  {}
  
const isObject = x =>
  Object (x) === x
  
const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, empty ]
      )
    .reduce
      ( (acc, [ k, v ]) =>
          v === empty
            ? acc
            : { ...acc, [k]: v }
      , empty
      )

diff1 accepts two input objects and a relationship descriptor, rel. This descriptor defaults to "left" which is the default "orientation" of the comparison. Below, notice that diff1 only provides half of the result we need. Reversing the arguments in a second call to diff1 provides the other half.

const x =
  { a: 1, b: 2, c: 3 }

const y =
  { a: 1, b: 3, d: 4 }
  
console.log (diff1 (x, y, "left"))
// { b: { left: 2 }, c: { left: 3 } }

console.log (diff1 (y, x, "right"))
// { b: { right: 3 }, d: { right: 4 } }

Also worth noting is the relationship labels "left" and "right" are user-definable. For example, if you have a known relationship between the objects you're comparing and you wish to provide more descriptive labels in the diff output ...

const customDiff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "original")
    , diff1 (y, x, "modified")
    )

customDiff
    ( { host: "localhost", port: 80 }
    , { host: "127.0.0.1", port: 80 }
    )
// { host: { original: 'localhost', modified: '127.0.0.1' } }

In the above example, it may be easier to work with the output in other areas of your program because labels original and modified are more descriptive than left and right.

merge

All that remains is merging the two half diffs into a complete result. Our merge function also works generically and accepts any two objects as input.

const x =
  { a: 1, b: 1, c: 1 }

const y =
  { b: 2, d: 2 }

console.log (merge (x, y))
// { a: 1, b: 2, c: 1, d: 2 }

In the event each object contains a property whose value is also an object, merge will recur and merge the nested objects as well.

const x =
  { a: { b: { c: 1, d: 1 } } }

const y =
  { a: { b: { c: 2, e: 2 } }, f: 2 }

console.log (merge (x, y))
// { a: { b: { c: 2, d: 1, e: 2 } }, f: 2 }

Below we encode our intentions in merge

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .reduce
      ( (acc, [ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? { ...acc, [k]: merge (left [k], v) }
            : { ...acc, [k]: v }
      , left
      )

And that's the whole kit and caboodle! Expand the code snippet below to run a code demonstration in your own browser

const empty =
  {}

const isObject = x =>
  Object (x) === x

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, empty ]
      )
    .reduce
      ( (acc, [ k, v ]) =>
          v === empty
            ? acc
            : { ...acc, [k]: v }
      , empty
      )

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .reduce
      ( (acc, [ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? { ...acc, [k]: merge (left [k], v) }
            : { ...acc, [k]: v }
      , left
      )

const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    )

const x =
  { a: { b: { c: 1, d: 2, e: 3 } } }

const y =
  { a: { b: { c: 1, d: 3, f: 4 } } }

console.log (diff (x, y))
// { a: { b: { d: { left: 2, right: 3 }, e: { left: 3 }, f: { right: 4 } } } }

console.log (diff (diff (x,y), diff (x,y)))
// {} 

remarks

As we look back at our diff function, I want to highlight one important part of its design. A good portion of the work is handled by the merge function which is completely separate from diff, yet a tough nut to crack on its own. Because we separated our concerns into singular functions, it's now easy to reuse them in other areas of your program. Where we wanted diff, we got it, and we got intuitive deep merge functionality for free.


extra: support for arrays

Our diff function is very convenient as it can crawl deeply nested objects, but what if one of our object properties is an array? It'd be nice if we could diff arrays using the same technique.

Supporting this feature requires non-trivial changes to the code above. However, the majority of the structure and reasoning stays the same. For example, diff is completely unchanged

// unchanged
const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    )

To support arrays in merge, we introduce a mutation helper mut which assigns a [ key, value ] pair to a given object, o. Arrays are considered objects too, so we can update both arrays and objects using the same mut function

const mut = (o, [ k, v ]) =>
  (o [k] = v, o)

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (mut, left)

Shallow merges work as expected

const x =
  [ 1, 2, 3, 4, 5 ]

const y =
  [ , , , , , 6 ]

const z =
  [ 0, 0, 0 ]

console.log (merge (x, y))
// [ 1, 2, 3, 4, 5, 6 ]

console.log (merge (y, z))
// [ 0, 0, 0, <2 empty items>, 6 ]

console.log (merge (x, z))
// [ 0, 0, 0, 4, 5, 6 ]

And deep merges too

const x =
  { a: [ { b: 1 }, { c: 1 } ] }

const y =
  { a: [ { d: 2 }, { c: 2 }, { e: 2 } ] }

console.log (merge (x, y))
// { a: [ { b: 1, d: 2 }, { c: 2 }, { e: 2 } ] }

Supporting arrays in diff1 is considerably more challenging

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          Object.keys (v) .length !== 0
      )
    .reduce
      ( mut
      , isArray (left) && isArray (right) ? [] : {}
      )

But with these changes in place, we can now deeply compare objects that contain arrays – and even arrays containing objects!

const x =
  { a: 1, b: [ { c: 1 }, { d: 1 }, { e: 1 } ] }

const y =
  { a: 1, b: [ { c: 2 }, { d: 1 }, 5, 6 ], z: 2 }

console.log (diff (x, y))
// { b:
//     [ { c: { left: 1, right: 2 } }
//     , <1 empty item>
//     , { left: { e: 1 }, right: 5 }
//     , { right: 6 }
//     ]
// , z: { right: 2 } 
// }

Because diff1 carefully changes its behavior based on its input types, we get array diffing for free

const x =
  [ 1, 2, 3, 4 ]

const y =
  [ 1, 2, 9 ]

const z =
  [ 1, 2, 9 ]

console.log (diff (x, y))
// [ <2 empty items>, { left: 3, right: 9 }, { left: 4 } ]

console.log (diff (y, z))
// []

Run the full program in your browser below

const isObject = x =>
  Object (x) === x

const isArray =
  Array.isArray

const mut = (o, [ k, v ]) =>
  (o [k] = v, o)

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, diff1 (v, right[k], rel) ]
            : right[k] !== v
              ? [ k, { [rel]: v } ]
              : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          Object.keys (v) .length !== 0
      )
    .reduce
      ( mut
      , isArray (left) && isArray (right) ? [] : {}
      )

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (mut, left)


const diff = (x = {}, y = {}) =>
  merge
    ( diff1 (x, y, "left")
    , diff1 (y, x, "right")
    )

const x =
  { a: 1, b: [ { c: 1 }, { d: 1 }, { e: 1 } ] }

const y =
  { a: 1, b: [ { c: 2 }, { d: 1 }, 5, 6 ], z: 2 }

console.log (diff (x, y))
// { b:
//     [ { c: { left: 1, right: 2 } }
//     , <1 empty item>
//     , { left: { e: 1 }, right: 5 }
//     , { right: 6 }
//     ]
// , z: { right: 2 } 
// }

shallow diff

The previous version of this answer provided an object diff function for comparing objects with the same keys and comparing objects with different keys, but neither solution performed the diff recursively on nested objects.

recursive intersection

In this related Q&A, we take two input objects and compute a recursive intersect instead of diff.

Unsustainable answered 20/10, 2015 at 9:34 Comment(13)
check that: console.log(diffObject({A: 10, B: 20, C: 30, D: 90}, {A: 10, B: 22, C: 30}));Hiro
@SheraliTurdiyev your comment has been addressed. diffObject will now compare two objects with asymmetrical keysUnsustainable
all is ok, but what would be, if B from first obj and B from second are arrays too?Ivar
@brabertaser1992 try it and see, then tell me what you understand about the output.Unsustainable
yep, didn't work if {A: 10, B: 20, C: 30, D: ["abc", "def"]} & {A: 10, B: 20, C: 32, D: ["abc", "def"]} as result C is returned (it's ok) and D: ["abc", "def"] - and this is not ok(Ivar
Now tell me why that happenedUnsustainable
@brabertaser1992 lol, I'm not making jokes. I'm asking you to put some effort into understand why instead of giving you all the answers.Unsustainable
via the reduce function?Ivar
@brabertaser1992 what is the return value of [1,2,3] === [1,2,3] ?Unsustainable
@naomik yes you are right, i need somehow to check what exactly type i'm currently comparingIvar
if (a[k] !== b[k]){ if (angular.isArray(b[k])){ if (a[k].toString() !== b[k].toString()){ map[k] = b[k]; } } else{ map[k] = b[k]; } } return map; seems that such code worksIvar
Please check my answer, If you have any suggestions or problem, let me know. ThanksHiro
Maybe you can check for the type and if it's an object to call the function recursively on it. Have not tested it but it should work.Adena
M
3

The solution is quite simple,

Initialize your array,

var resultArray = [];

then cycle through the keys of your object, using one as a reference (Highly assuming the objects have the same keys, but you want to check on keys holding different values)

and finally run the simple code

for(let key in obj){
    // console.log(key);
    if(obj[key]  !== this.profileObject[key] ){
      resultArray.push(key);
    }
}

And collect your answer at the end

console.log(resultArray);
Mickel answered 11/9, 2018 at 22:7 Comment(0)
V
2

Try this

function getNewProperties(prevObj, newObj) {
  const prevObjProperties = Object.keys(prevObj);
  const newObjProperties = Object.keys(newObj);
  const newProperties = newObjProperties.filter(prop => prevObjProperties.indexOf(prop) === -1);
  return newProperties;
}
Vietnamese answered 2/4, 2019 at 18:56 Comment(1)
An explanation of what this does and how it works would be helpful. The approach from this answer ultimately helped me. The two objects I needed to compare would have the same keys but the values could be different. I changed const newProperties = newObjProperties.filter(prop => prevObjProperties.indexOf(prop) === -1); to const newProperties = newObjProperties.filter(prop => prevObj[prop] !== newObj[prop] );Cogitation
H
0

This will return the diff of the first argument with respect to the second argument. I am not using angular.forEach here though.

var x = {
   a : 1,
   b:2,
  c :3,
  d:4
 }

var y = {
   a : 1,
   b:4,
  c :3,
  d : 5
 };


var diff = function(x,y){
  var target = {};   
  var diffProps = Object.keys(x).filter(function(i){
    if(x[i] !== y[i]){
      return true;
    }
    return false;
   }).map(function(j){
       var obj = {};
       obj[j] = x[j];
       target = Object.assign(target,obj)
  });
   return target;
};


console.log(diff(x,y));
Hydroxyl answered 20/10, 2015 at 9:34 Comment(1)
This can be vastly simplified with a reduceUnsustainable
A
0
$scope.ar1 = {A: 10, B: 20, C: 30};

$scope.ar2 = {A: 10, B: 22, C: 30};

$scope.newObj = {};
angular.forEach($scope.ar1, function(v, i) {
    // if ar2[i] is exists and ar2[i] != v then put that value to newObj
    if ($scope.ar2[i] && $scope.ar2[i] != v) {
        $scope.newObj[i] = $scope.ar2[i];
    }
});

console.log($scope.newObj);

here is the DEMO

Alcalde answered 20/10, 2015 at 9:36 Comment(0)
B
0

This solution is not in angular, but it might help.

It will take 2 objects with any number of keys, and they need not contain the same keys.

**Output: ** The key:value pairs which are present in only one object and not the other and the key:value pairs which are present in both objects but the values are different.

var obj1 = {A: 10, B: 20, C: 30, E: 40};
var obj2 = {A: 11, B: 20, C: 30, D: 50};
var finalObject = {};

$( document ).ready(function() {
    var keysOfObj1 = Object.keys( obj1 );
    var keysOfObj2 = Object.keys( obj2 );

    var keys = [];
    keys = $( keysOfObj1 ).not( keysOfObj2 ).get(); // keys of first object not in second object

    for( var i=0;i<keys.length;i++ ) {
        finalObject[ keys[ i ] ] = obj1[ keys[ i ] ];
    }

    keys.length = 0; // reset the temp array

    keys = $( keysOfObj2 ).not( keysOfObj1 ).get(); // keys of second object not in first object

    for( var i=0;i<keys.length;i++ ) {
        finalObject[ keys[ i ] ] = obj2[ keys[ i ] ];
    }

    keys.length = 0; // reset the temp array again

    if( keysOfObj1.length != keysOfObj2.length ) {
        // case already handled above
    }

    for( var i in obj1 ) {
        if( obj1.hasOwnProperty( i ) ) {
            if( obj2.hasOwnProperty( i ) ) {
                if( obj1[ i ] != obj2[ i ] ) {
                    finalObject[ i ] = obj2[ i ];
                } else {
                    // the property has the same value in both objects, all is well...
                }
            } else {
                // case already handled above
            }
        } else {
            // case already handled above
        }
    }
    console.log( obj1 );
    console.log( obj2 );
    console.log( finalObject );

Hope it helps.

Bornie answered 20/10, 2015 at 10:5 Comment(0)
L
0

I hope this will help you. I did it with jQuery each function.

var a = {A: 10, B: 20, C: 30};
var b = {A: 10, B: 22, C: 30};
var hasObj = false; //Declaring variable outside for onetime memory allocation.

$.each(b, function(keyOfB, valOfB) {

    hasObj = false;  //Assigning false for each parent loop

    $.each(a, function(keyOfA, valOfA) {
        if (keyOfA == keyOfB && valOfA == valOfB) {
            hasObj = true;
            return false; //If key and value mathed loop will break and no remaining items of second array will be check.
        }
    });

    if (hasObj == false) {
        console.log(keyOfB + "--" + valOfB); //Printing the unmatched key and value
    }

});
Landbert answered 20/10, 2015 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.