Don P
Don P

Reputation: 63728

Merge two objects, but explicitly define the keys that can be set

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.

Upvotes: 0

Views: 253

Answers (5)

motanelu
motanelu

Reputation: 4025

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)

Upvotes: 1

Don P
Don P

Reputation: 63728

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
    };
    

Upvotes: 0

Vojtech
Vojtech

Reputation: 2816

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;

Upvotes: 0

SimpleJ
SimpleJ

Reputation: 14778

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);

Upvotes: 0

user3094755
user3094755

Reputation: 1641

Try...

function (Obj1,Obj2) {
  ['can','set','these'].forEach(key => Obj1[key] && ( Obj2[key] = Obj1[key] ) )
}

Upvotes: 0

Related Questions