27

There is _.merge functionality in lodash. I want to achieve the same thing in ES6 or ES7.

Having this snippet:

Object.assign({}, {key: 2}, {key: undefined})

I want to receive {key: 2}. Currently I receive {key: undefined}

This is NOT a deep merge.

Is it possible? If yes then how to achieve that?

Community
  • 1
  • 1
Filip Bartuzi
  • 5,711
  • 7
  • 54
  • 102
  • Note: this question does not cover my case with undefined: http://stackoverflow.com/questions/13852852/surely-es6-must-have-a-way-to-merge-two-javascript-objects-together-what-is-it?noredirect=1&lq=1 – Filip Bartuzi Oct 11 '16 at 12:29
  • You can take the second object, prune any values that are `undefined` and then use it in `Object.assign` – VLAZ Oct 11 '16 at 12:47
  • @vlaz is there native ES6 way to prune undefiend values from object? – Filip Bartuzi Oct 11 '16 at 12:52
  • Well, if you take ES5 with just a dash of ES6 then `arr.filter(x => typeof x !== "undefined" )` – VLAZ Oct 11 '16 at 12:53
  • gah, sorry, forgot it's an object: `Object.keys(obj).reduce(key => typeof obj[key] !== "undefined" ).reduce( (memo, key) => (memo[key] = obj[key]; memo), {})` admittedly, doesn't look as pretty when in one line but it's pretty straight forward. – VLAZ Oct 11 '16 at 12:56
  • 2
    Why does your object have undefined values in it? Just wondering. –  Oct 11 '16 at 13:04
  • @torazaburo very good bug catch. That was the issue in my case. I should not provide undefined values in the first place – Filip Bartuzi Oct 12 '16 at 11:56
  • "This is NOT a deep merge." How is this not a deep merge? It's simply a deep merge with slightly different behavior, and in my opinion, behavior that makes more sense. – Charles Offenbacher Nov 16 '17 at 20:27

6 Answers6

11

You can't achieve that with a straight usage of Object.assign, because each next object will rewrite the same keys for prev merge. The only way, to filter your incoming objects with some hand-crafted function.

function filterObject(obj) {
    const ret = {};
    Object.keys(obj)
        .filter((key) => obj[key] !== undefined)
        .forEach((key) => ret[key] = obj[key]);
    return ret;
}
steppefox
  • 1,784
  • 2
  • 14
  • 19
11

You can simply filter out the keys with undefined values before passing them to Object.assign():

const assign = (target, ...sources) =>
  Object.assign(target, ...sources.map(x =>
    Object.entries(x)
      .filter(([key, value]) => value !== undefined)
      .reduce((obj, [key, value]) => (obj[key] = value, obj), {})
  ))

console.log(assign({}, {key: 2}, {key: undefined}))
Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
5

Write a little utility to remove undefined values:

function removeUndefined(obj) {
  for (let k in obj) if (obj[k] === undefined) delete obj[k];
  return obj;
}

Then

Object.assign({}, {key: 2}, removeUndefined({key: undefined}))

This seems preferable to writing your own assign with wired-in behavior to remove undefined values.

1

use lodash to omit nil values and then combine the two objects into one via spread

{ ...(omitBy({key: 2}, isNil)), ...(omitBy({key: undefined}, isNil))}

See more info on lodash here https://lodash.com/docs/4.17.15

Ray
  • 1,134
  • 10
  • 27
1

If you just need the values and don't need an object, you could also use object destructuring:

const input = { a: 0, b: "", c: false, d: null, e: undefined };
const { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 } = input;
console.log(a, b, c, d, e, f);
// => 0, "", false, null, 5, 6

This will only override absent or undefined values.

I often use this for function argument default values like this:

function f(options = {}) {
  const { foo = 42, bar } = options;
  console.log(foo, bar);
}

f();
// => 42, undefined

f({})
// => 42, undefined

f({ foo: 123 })
// => 123, undefined

f({ bar: 567 })
// => 42, 567

f({ foo: 123, bar: 567 })
// => 123, 567
Benjamin M
  • 23,599
  • 32
  • 121
  • 201
0

With ES2019/ES10's new object method, Object.fromEntries(), Michał's answer can be updated:

const assign = (target, ...sources) =>
  Object.assign(target, ...sources.map(x =>
    Object.fromEntries(
      Object.entries(x)
        .filter(([key, value]) => value !== undefined)
    )
  ))

console.log(assign({}, {key: 2}, {key: undefined}))
JohnMarkT
  • 36
  • 4