0

I want to merge two objects into a new object, with one object overwriting properties if both have them.

This is very easy to do with ES6 spreads, but in this particular case I would like to use a method that still defines what keys can be set. Basically create a "white list" of properties that are allowed on my final object. (With spreads, any property would be merged into the new object).

Is there a method that uses ES6+ to do this?

Two objects:

var o1 = { 
  a: 'foo',
  b: 'bar',
  c: 'baz'
}

var o2 = { 
  a: 'pop',
  bad_property: 'dont copy me'
}

var oMerged = {
  a: 'pop', // I came from obj_2
  b: 'bar',
  c: 'baz'
}

Merge methods:

// Method 1: Use spreads, problem is this hides the keys.
var oMerged = { 
  ...o1,
  ...o2
};

// Method 2: Shows each key, but requires a ternary to check if obj_2 has the key.
var oMerged = {
  a: (o2.a !== undefined) ? o2.a : o1.a,
  b: (o2.b !== undefined) ? o2.b : o1.b,
  c: (o2.c !== undefined) ? o2.c : o1.c
}

// Method 3: Define what keys can be merged, but use a more concise syntax / possibly an ES6+ feature.
??? 

Edit: Explicitly said that the object should have a white list of acceptable properties. There are good answers but they require black lists of unacceptable properties. The reason for the 'white list' is it shows other developers the shape of the final object.

Don P
  • 60,113
  • 114
  • 300
  • 432
  • Are you sure you're not looking for [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)? Can you explain what you need this for? – Mike 'Pomax' Kamermans Apr 03 '17 at 21:57
  • Looking through Object.assign again, but if I remember correctly it will copy all enumerable properties from any objects - so it would include 'bad_property' from obj_2. – Don P Apr 03 '17 at 21:59
  • Have a look at this: [JavaScript filter for objects](http://stackoverflow.com/questions/5072136/javascript-filter-for-objects) – trincot Apr 03 '17 at 22:22

5 Answers5

1

Here you go, you can use the factory() function to create different merge functions, each with its own set of allowed words and then call those functions to merge an arbitrary number of objects.

const obj1 = { 
  a: 'foo',
  b: 'bar',
  c: 'baz'
}

const obj2 = { 
  a: 'pop',
  bad_property: 'dont copy me'
}

const obj3 = {
  another_one: 'test',
  another_bad_property: 'dont copy this either'
}

const factory = allowed => {
  return (...args) => {
    const temp = {}
    args.forEach(obj => {
      Object.keys(obj).forEach(key => {
        if (allowed.indexOf(key) !== -1) {
          temp[key] = obj[key]
        }
      })
    })
    
    return temp
  }
}

const mergeWith4AllowedKeys = factory(['a', 'b', 'c', 'another_one'])
const merged = mergeWith4AllowedKeys(obj1, obj2, obj3)

console.log(merged)

const mergeWith3AllowedKeys = factory(['a', 'b', 'c'])
const secondBatch = mergeWith3AllowedKeys(obj1, obj2) // only 2

console.log(secondBatch)
motanelu
  • 3,945
  • 1
  • 14
  • 21
  • Thansk @motanelu this is a pretty solid answer - the main issue is that it requires a list of banned properties. Instead of a black list of banned properties, I want a white list of acceptable properties. The purpose is to show other developers the 'shape' of the final object. – Don P Apr 03 '17 at 22:22
0

Try...

function (Obj1,Obj2) {
  ['can','set','these'].forEach(key => Obj1[key] && ( Obj2[key] = Obj1[key] ) )
}
user3094755
  • 1,561
  • 16
  • 20
0

You could use an omit function (such as this one provided by lodash) to create a clone of an object without certain keys. Then you could merge that object:

function omit(object, blacklist) {
  return Object.keys(object).reduce((result, key) => {
    if(!blacklist.includes(key)) {
      result[key] = object[key];
    }
    return result;
  }, {});
}

var o1 = {
  a: 'foo',
  b: 'bar',
  c: 'baz'
}

var o2 = { 
  a: 'pop',
  bad_property: 'dont copy me'
}

var blacklist = ['bad_property'];
var merged = {
  ...omit(o1, blacklist),
  ...omit(o2, blacklist)
};

console.log(merged);
SimpleJ
  • 13,812
  • 13
  • 53
  • 93
0

This is ES2017 Spread and Rest implementation, it is supported for example in typescript ^2.1

With o1 and o2 as you have it, you can write:

let merged = { ...o1, ...o2 };
// and then use Object rest as the dual of object spreads, to pick up the wanted properties
let { bad_property, ...oMerged } = merged;
Vojtech
  • 2,756
  • 2
  • 19
  • 29
  • Interesting, but wouldn't this require an explicit list of "bad" properties? Basically a black list instead of a white list? – Don P Apr 03 '17 at 22:19
  • yes I assume you could have it in some blacklist object and use it like `let { ...badProps, ...oMerged } = merged;` all other keys that are not in badProps should go to that empty just declared `oMerge`, but I haven't tried this version yet – Vojtech Apr 03 '17 at 22:22
  • Ok so after some research, this solution will work only when you explicitly type the "bad" properties when using the rest operator – Vojtech Apr 03 '17 at 22:32
0

This is the shortest method I've come up with that will let you

  1. set an order for properties to be overwritten
  2. let you declare what properties are acceptable in the final object
  3. not require long ternaries for each property

    // Step 1. merge all properties into a new object.
    var oMerged = {...o1, ...o2};
    
    // Step 2. Explicitly use keys to assign
    var oFinal = {
      a: oM.a,
      b: oM.b,
      c: oM.c
    };
    
Don P
  • 60,113
  • 114
  • 300
  • 432