7

ES6 has a lot of functions including assign and others. But is there a method to get a list of properties that are different from one object to the next?

For example, if I have a component with two states. The default state has 100 properties that define it. State two there are only 10 properties that change. Let's say I get 2 objects containing all 100 properties. I want to create object 3 that has only the 10 properties that have changed (actually not only the properties that changed but the properties on the second object - see update).

The second object keeps all its unique properties and overrides the properties in the first.

I thought Object.assign() might do this but I don't think so.

    var object = {name:Fred, age: 20, weight: 100};
    var object2 = {name:Fred, age: 21, weight: 120};

    function getChangesFromObjectTwo(object1, object2) {

        return object;
    }

    // returns {age:21, weight: 120};
    var changes = getChangesFromObjectTwo(object, object2);

UPDATE:
Great answers. I wasn't specific enough... If object2 has additional properties they should show on the returned object.

    var object = {name:Fred, age: 20, weight: 100};
    var object2 = {name:Fred, age: 21, weight: 120, height: 70};

    function getChangesFromObjectTwo(object1, object2) {

        return object;
    }

    // returns {age:21, weight: 120, height: 70};
    var changes = getChangesFromObjectTwo(object, object2);
1.21 gigawatts
  • 16,517
  • 32
  • 123
  • 231
  • 2
    Approach this the other way - rather than ask "does ES6 have ", find a list of what it *does* have (e.g. http://es6-features.org) and read through that. – jonrsharpe May 25 '20 at 06:57
  • 2
    Or even better, ask "how can I do this" because that's what you actually want to know and don't worry about ES6 at all – if there's a specific method in ES6 that does it then surely someone will mention it. – Guy Incognito May 25 '20 at 06:59
  • 1
    @jonrshape - what a bad advise. With that, we could tear down the whole site because one could always and ever argue: instead of asking *how* it works, read and understand the documentation/books/source code etc. That was really not helpful. – Andreas Dolk May 25 '20 at 07:01

4 Answers4

7

Doesn't have to be ES6, but you can implement it like this:

var object = {name: 'Fred', age: 20, weight: 100};
var object2 = {name: 'Fred', age: 21, weight: 120, height: 70};

function getChangesFromObjectTwo(source, target) {
  return Object.fromEntries(Object.entries({...source, ...target})
        .filter(([key, value]) => !Object.is(source[key], value)));
}

// returns {age:21, weight: 120};
var changes = getChangesFromObjectTwo(object, object2);
console.log(changes);

Added properties also included

P.S. Using Object.is to bypass NaN problem

1.21 gigawatts
  • 16,517
  • 32
  • 123
  • 231
Hao Wu
  • 17,573
  • 6
  • 28
  • 60
4

Assuming that keys are identical in both objects o1 and o2, you can just use Object.keys() with a a reduce() operation:

Object.keys(o2).reduce((a, k) => (o1[k] !== o2[k] && (a[k] = o2[k]), a), {});

Full snippet:

const object1 = {name:'Fred', age: 20, weight: 100};
const object2 = {name:'Fred', age: 21, weight: 120};

function getChanges(o1, o2) {
  return Object.keys(o2)
               .reduce((a, k) => (o1[k] !== o2[k] && (a[k] = o2[k]), a), {});
}

console.log(getChanges(object1, object2));

Or if you're working in an environment that supports Object.entries(), you can avoid a couple of lookups:

Object.entries(o2).reduce((a, [k, v]) => (o1[k] !== v && (a[k] = v), a), {});

Full snippet:

const object1 = {name:'Fred', age: 20, weight: 100};
const object2 = {name:'Fred', age: 21, weight: 120};

function getChanges(o1, o2) {
  return Object.entries(o2)
               .reduce((a, [k, v]) => (o1[k] !== v && (a[k] = v), a), {});
}

console.log(getChanges(object1, object2));
Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156
2

Object.assign does the opposite of what you want: it modifies an object based on an object listing changes.

There's no built-in function that does it, but you can easily implement your own.

For a single level depth observation, it should be enough to:

function getChangesFromObjectTwo(obj1, obj2){
  //Using Set to create an unique list
  return [...new Set([
      ...Reflect.ownKeys(obj1),
      ...Reflect.ownKeys(obj2)
    ])]
    .map(k => [k, obj1[k], obj2[k]])
    .filter(([, v1, v2]) => v1 !== v2)
    .reduce((acc, [k, , v]) => (
      acc[k] = v,
      acc
    ), {})
}

var object = {name:"Fred", age:20, weight: 100};
var object2 = {name:"Fred", age:21, weight: 120, height: 70};

console.log(getChangesFromObjectTwo(object, object2));
FZs
  • 16,581
  • 13
  • 41
  • 50
  • This works if the properties are the same but if object 2 has additional properties `var object2 = {name:"Fred", age:21, weight: 120, height: 70};`. My mistake. Edited question. – 1.21 gigawatts May 25 '20 at 08:04
  • 1
    @1.21gigawatts Thanks for pointing this out, and your previous edit! I've fixed it... (I intended to add a snippet, but I was on mobile, and "*The snippet editor doesn't support touch devices*") – FZs May 25 '20 at 08:14
2

I believe Proxy is the tool you are looking for.

In your case, if you have

const defaultObject = {name: "Fred", age: 20, weight: 100}; // 100+ properties
var clonedObject = {...defaultObject}; // shallow copy

you can create monitor object and proxy object for them:

var monitor = {};
const proxy = new Proxy(clonedObject, {
  set: function(obj, prop, value) {
    obj[prop] = value;
    if(defaultObject[prop]!==value) monitor[prop] = value;
    else delete monitor[prop];
  }
});

Now if you change anything in proxy...

proxy.age = 21;
proxy.weight = 120;
proxy.age = 20; // restore default
proxy.height = 70; // new value

...you can see the changes in monitor

console.log(monitor); // weight: 120, height: 70
console.log(clonedObject); // proxy passes the changes to clonedObject

Updated snippet you requested:

const defaultObject = {name: "Fred", age: 20, weight: 100};
var clonedObject = {...defaultObject}; // shallow copy


var monitor = {};
const proxy = new Proxy(clonedObject, {
  set: function(obj, prop, value) {
    obj[prop] = value;
    if(defaultObject[prop]!==value) monitor[prop] = value;
    else delete monitor[prop];
  }
});


proxy.age = 21;
proxy.weight = 120;
proxy.age = 20; // restore default
proxy.height = 70; // new value


console.log(monitor); // weight: 120, height: 70
console.log(clonedObject);
Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
  • Didn't know you could use proxy like this. Can you demo with code above to get same results? – 1.21 gigawatts May 25 '20 at 07:57
  • @1.21gigawatts yes, it works the same if you add values (unless the value is `undefined` which should be reserved for undefined values). I strongly recommend to use Proxy as it is O(c), whereas the other answers are O(n) which is horrible if you have many properties. – Jan Turoň May 25 '20 at 08:31
  • That time complexity comparison is disingenuous. You're just looking at the time complexity of getting the changed properties, but fail to mention that you have an increase in time complexity when manipulating the object due to all calls being proxied, and an increase in space complexity by maintaining both a proxy and a parallel object containing changes. – Robby Cornelissen May 26 '20 at 01:24
  • @RobbyCornelissen proxy object doesn't change (see it is const). Building monitor object in place every time you need to examine changes is like rebuilding database index every new select. Setting/adding object property is implementation dependent, but tends to be constant. Imagine some coder picks your answer as a black box and calls it every frame. – Jan Turoň May 26 '20 at 11:03
  • All I said is that there's a trade-off. Conversely, imagine some coder picks your solution as a black box, ends up with double memory usage for every object, and only needs to find different properties once. – Robby Cornelissen May 26 '20 at 11:21