12

I'm trying to find a way of creating an object where upon creation it ignores the values that are undefined.

In the example below, the variable someNames has unknown content when creating the object.

const someNames = {
  catName: 'purry',
  rabbitName: 'floppy',
  turtleName: 'shelly'
};

const { catName, dogName, hamsterName, rabbitName } = someNames;

const animalNames = Object.assign({}, {
  catName,
  dogName,
  hamsterName,
  rabbitName
});

console.log(animalNames);// {catName: 'purry', rabbitName: 'floppy'}

What actually gets logged is this:

{
  catName: 'purry',
  dogName: undefined,
  hamsterName: undefined,
  rabbitName: 'floppy'
}
silverlight513
  • 5,268
  • 4
  • 26
  • 38
  • this might help but you'd have to give up the `const`: http://stackoverflow.com/q/286141/2266261 – Dunno Sep 15 '16 at 14:37
  • You could create your own `pick` function that creates an object out of the specified keys that only have values different from `undefined` – MinusFour Sep 15 '16 at 14:38
  • 1
    @Dunno that would work with `const` as well? – Bergi Sep 15 '16 at 14:50
  • "*creating an object where upon creation it ignores the values that are undefined.*" - what do you need this for? Often `undefined` is the more sensible default, keeping the same shape of objects everywhere. – Bergi Sep 15 '16 at 14:52
  • Why don't you just use `const animalNames = Object.assign({}, someNames)`, are there more properties in `someNames` than which you need? – Bergi Sep 15 '16 at 14:57
  • @Bergi, yeah, sorry I didn't illustrate that in the question. I've updated the question. The reason I want the new object without undefined is because I'm doing `Object.keys(animalNames).length` in an if statement – silverlight513 Sep 15 '16 at 15:07

6 Answers6

9

Don't use Object.assign which copies all own enumerable properties regardless of their value but your own custom function that can filter out undefined properties:

function assignDefined(target, ...sources) {
    for (const source of sources) {
        for (const key of Object.keys(source)) {
            const val = source[key];
            if (val !== undefined) {
                target[key] = val;
            }
        }
    }
    return target;
}

…
const animalNames = assignDefined({}, {
  catName,
  dogName,
  hamsterName,
  rabbitName
});
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • You could theoretically only allow two parameters instead of having as many as possible. That way, you can save a loop. Instead when using the method just use the spread operator when creating the object inside the function call. – silverlight513 Sep 16 '16 at 07:49
  • @silverlight513 I wanted to make the function work like `Object.assign`; of course you can remove the outer loop if you don't need it. Not sure what you mean by the spread operator. – Bergi Sep 16 '16 at 07:58
  • That's probably the best idea for everyone tbh, just my personal preference. By the spread opperator I mean doing something like this `assignDefined({}, {catName, ...otherObject, dogName})`. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator – silverlight513 Sep 16 '16 at 08:05
  • @silverlight513 there is no spread operator for objects in ES6 (if it works for you then it's because you've enabled experimental plugins in your babel settings) – Bergi Sep 16 '16 at 08:07
  • ahh, I have been. I didn't even realize. Kinda sucks how array spreads are in ES6 but objects aren't. – silverlight513 Sep 16 '16 at 08:16
3

Old thread, however not quite pleased with previous answers. Thought I'd chip in on a cleaner solution:

const someNames = {
  catName: 'purry',
  rabbitName: 'floppy',
  notAnAnimal: 'car',
};

const allowedKeys = ['catName', 'rabbitName'];
const animalNames = Object.keys(someNames).reduce((animals, key) => {
  if (allowedKeys.includes(key)) animals[key] = someNames[key];
  return animals;
}, {});
nomadoda
  • 4,561
  • 3
  • 30
  • 45
2

You could do something like this just to clean those properties

Object.keys(animalNames).forEach(function (key) {
    if(animalNames[key] === undefined) delete animalNames[key];
});
taguenizy
  • 2,140
  • 1
  • 8
  • 26
2

This is what I went for afterwards. It's just a refactor of @Bergi's code. I've done it this way because it has a reduced number of loops. @Bergi's answer allows for as many object parameters as possible. In this solution, you can just use spreads to create the second object if you have object spreads enabled in babel (https://babeljs.io/docs/plugins/transform-object-rest-spread/).

  const someNames = {
    catName: 'purry',
    rabbitName: 'floppy',
    turtleName: 'shelly'
  };

  function assignDefined(target, source) {

    Object.keys(source).map((key, index) => {
      if(source[key] !== undefined) {
        target[key] = source[key];
      }
    });

    return target;
  }

  const { catName, dogName, hamsterName, rabbitName } = someNames;
  const animalNames = assignDefined({}, {
    catName,
    dogName,
    hamsterName,
    rabbitName
  });

  console.log(animalNames);

Thanks @Bergi!

silverlight513
  • 5,268
  • 4
  • 26
  • 38
1

'use strict';

const someNames = {
  catName: 'purry',
  rabbitName: 'floppy'
};

const expectedProperties = ['catName', 'dogName', 'hamsterName', 'rabbitName'];
const animalNames = Object.assign({}, expectedProperties.reduce((o, prop) => (someNames[prop]) ? (o[prop] = someNames[prop]) && o : o, {}));

console.log(animalNames);
Diego ZoracKy
  • 2,227
  • 15
  • 14
  • 1
    careful though, The valid value of zero is falsy so you can't just check for truthiness, you need to explicitly check for undefined or null values – Ben Wheeler Oct 26 '18 at 12:44
0

I think you can do that:

const someNames = {
  catName: 'purry',
  rabbitName: 'floppy',
  turtleName: 'shelly'
};

const { catName, dogName, hamsterName, rabbitName } = someNames;

const animalNames = Object.assign({}, Object.fromEntries(Object.entries({
  catName,
  dogName,
  hamsterName,
  rabbitName
}).filter(([key,value])=>value!==undefined)));

console.log(animalNames);// {catName: 'purry', rabbitName: 'floppy'}
JYC
  • 343
  • 3
  • 8