In JavaScript, how to conditionally add a member to an object?

Viewed 447773

I would like to create an object with a member added conditionally. The simple approach is:

var a = {};
if (someCondition)
    a.b = 5;

Now, I would like to write a more idiomatic code. I am trying:

a = {
    b: (someCondition? 5 : undefined)
};

But now, b is a member of a whose value is undefined. This is not the desired result.

Is there a handy solution?

Update

I seek for a solution that could handle the general case with several members.

a = {
  b: (conditionB? 5 : undefined),
  c: (conditionC? 5 : undefined),
  d: (conditionD? 5 : undefined),
  e: (conditionE? 5 : undefined),
  f: (conditionF? 5 : undefined),
  g: (conditionG? 5 : undefined),
 };
26 Answers
const obj = {
   ...(condition) && {someprop: propvalue},
   ...otherprops
}

Live Demo:

const obj = {
  ...(true) && {someprop: 42},
  ...(false) && {nonprop: "foo"},
  ...({}) && {tricky: "hello"},
}

console.log(obj);

I suggest the following:

const a = {
   ...(someCondition? {b: 5}: {})
}

Conditionally Add a member to an Object

const trueCondition = true;
const falseCondition = false;
const obj = {
  ...(trueCondition && { student: 10 }),
  ...(falseCondition && { teacher: 2 }),
};

// { student: 10 }

Perfomance test

Classic approach

const a = {};
if (someCondition)
    a.b = 5;

VS

spread operator approach

const a2 = {
   ...(someCondition && {b: 5})
}

Results:

The classic approach is much faster, so take in consideration that the syntax sugaring is slower.

testClassicConditionFulfilled(); // ~ 234.9ms
testClassicConditionNotFulfilled(); // ~493.1ms
testSpreadOperatorConditionFulfilled(); // ~2649.4ms
testSpreadOperatorConditionNotFulfilled(); // ~2278.0ms

function testSpreadOperatorConditionFulfilled() {
  const value = 5;

  console.time('testSpreadOperatorConditionFulfilled');
  for (let i = 0; i < 200000000; i++) {
    let a = {
      ...(value && {b: value})
    };
  }
  console.timeEnd('testSpreadOperatorConditionFulfilled');
}

function testSpreadOperatorConditionNotFulfilled() {
  const value = undefined;

  console.time('testSpreadOperatorConditionNotFulfilled');
  for (let i = 0; i < 200000000; i++) {
    let a = {
      ...(value && {b: value})
    };
  }
  console.timeEnd('testSpreadOperatorConditionNotFulfilled');
}

function testClassicConditionFulfilled() {
  const value = 5;

  console.time('testClassicConditionFulfilled');
  for (let i = 0; i < 200000000; i++) {
    let a = {};
    if (value)
        a.b = value;
  }
  console.timeEnd('testClassicConditionFulfilled');
}

function testClassicConditionNotFulfilled() {
  const value = undefined;

  console.time('testClassicConditionNotFulfilled');
  for (let i = 0; i < 200000000; i++) {
    let a = {};
    if (value)
        a.b = value;
  }
  console.timeEnd('testClassicConditionNotFulfilled');
}

testClassicConditionFulfilled(); // ~ 234.9ms
testClassicConditionNotFulfilled(); // ~493.1ms
testSpreadOperatorConditionFulfilled(); // ~2649.4ms
testSpreadOperatorConditionNotFulfilled(); // ~2278.0ms

more simplified,

const a = {
    ...(condition && {b: 1}) // if condition is true 'b' will be added.
}

What about using Enhanced Object Properties and only set the property if it is truthy, e.g.:

[isConditionTrue() && 'propertyName']: 'propertyValue'

So if the condition is not met it doesn't create the preferred property and thus you can discard it. See: http://es6-features.org/#ComputedPropertyNames

UPDATE: It is even better to follow the approach of Axel Rauschmayer in his blog article about conditionally adding entries inside object literals and arrays (http://2ality.com/2017/04/conditional-literal-entries.html):

const arr = [
  ...(isConditionTrue() ? [{
    key: 'value'
  }] : [])
];

const obj = {
  ...(isConditionTrue() ? {key: 'value'} : {})
};

Quite helped me a lot.

This is probably the shortest solution with ES6

console.log({
   ...true && {foo: 'bar'}
})
// Output: {foo:'bar'}
console.log({
   ...false && {foo: 'bar'}
})
// Output: {}

You can add all your undefined values with no condition and then use JSON.stringify to remove them all :

const person = {
  name: undefined,
  age: 22,
  height: null
}

const cleaned = JSON.parse(JSON.stringify(person));

// Contents of cleaned:

// cleaned = {
//   age: 22,
//   height: null
// }

I made a small benchmark with one other option. I like to remove "dead weight" from some objects. Usually falsy values.

Here are the benny results:

clean

const clean = o => {
    for (const prop in o) if (!o) delete o[prop];
}

clean({ value });

spread

let a = {
    ...(value && {b: value})
};

if

let a = {};
if (value) {
    a.b = value;
}

results

clean  :  84 918 483 ops/s, ±1.16%    | 51.58% slower    
spread :  20 188 291 ops/s, ±0.92%    | slowest, 88.49% slower    
if     : 175 368 197 ops/s, ±0.50%    | fastest

Below code snippet should work.

const a = {}

const conditionB = true;
const conditionC = true;
const conditionD = true;
const conditionE = true;

const b = {
  ...(conditionB && { b : 5}),
  ...(conditionC && { c : 5}),
  ...(conditionD && { d : 5}),
  ...(conditionE && { e : 5}),
 };

console.log(b);

Using lodash library, you can use _.omitBy

var a = _.omitBy({
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
}, _.IsUndefined)

This results handy when you have requests that are optional

var a = _.omitBy({
    b: req.body.optionalA,  //if undefined, will be removed
    c: req.body.optionalB,
}, _.IsUndefined)

This is the most succinct solution I can come up with:

var a = {};
conditionB && a.b = 5;
conditionC && a.c = 5;
conditionD && a.d = 5;
// ...

i prefere, using code this it, you can run this code

const three = {
  three: 3
}

// you can active this code, if you use object `three is null`
//const three = {}

const number = {
  one: 1,
  two: 2,
  ...(!!three && three),
  four: 4
}

console.log(number);

Wrap into an object

Something like this is a bit cleaner

 const obj = {
   X: 'dataX',
   Y: 'dataY',
   //...
 }

 const list = {
   A: true && 'dataA',
   B: false && 'dataB',
   C: 'A' != 'B' && 'dataC',
   D: 2000 < 100 && 'dataD',
   // E: conditionE && 'dataE',
   // F: conditionF && 'dataF',
   //...
 }

 Object.keys(list).map(prop => list[prop] ? obj[prop] = list[prop] : null)

Wrap into an array

Or if you want to use Jamie Hill's method and have a very long list of conditions then you must write ... syntax multiple times. To make it a bit cleaner, you can just wrap them into an array, then use reduce() to return them as a single object.

const obj = {
  X: 'dataX',
  Y: 'dataY',
  //...

...[
  true && { A: 'dataA'},
  false && { B: 'dataB'},
  'A' != 'B' && { C: 'dataC'},
  2000 < 100 && { D: 'dataD'},
  // conditionE && { E: 'dataE'},
  // conditionF && { F: 'dataF'},
  //...

 ].reduce(( v1, v2 ) => ({ ...v1, ...v2 }))
}

Or using map() function

const obj = {
  X: 'dataX',
  Y: 'dataY',
  //...
}

const array = [
  true && { A: 'dataA'},
  false &&  { B: 'dataB'},
  'A' != 'B' && { C: 'dataC'},
  2000 < 100 && { D: 'dataD'},
  // conditionE && { E: 'dataE'},
  // conditionF && { F: 'dataF'},
  //...

 ].map(val => Object.assign(obj, val))

Define a var by let and just assign new property

let msg = {
    to: "hito@email.com",
    from: "hifrom@email.com",
    subject: "Contact form",    
};

if (file_uploaded_in_form) { // the condition goes here
    msg.attachments = [ // here 'attachments' is the new property added to msg Javascript object
      {
        content: "attachment",
        filename: "filename",
        type: "mime_type",
        disposition: "attachment",
      },
    ];
}

Now the msg become

{
    to: "hito@email.com",
    from: "hifrom@email.com",
    subject: "Contact form",
    attachments: [
      {
        content: "attachment",
        filename: "filename",
        type: "mime_type",
        disposition: "attachment",
      },
    ]
}

In my opinion this is very simple and easy solution.

For the sake of completeness you can use Object.defineProperty() if you want to add additional descriptors. Note I purposely added enumerable: true otherwise the property wouldn't appear in the console.log(). The advantage with this approach is that you can also use Object.defineProperties() if you want to add multiple new properties (However, in this way every property will be dependent on the same condition...)

const select = document.getElementById("condition");
const output = document.getElementById("output");
let a = {};
let b = {};

select.onchange = (e) => {
  const condition = e.target.value === "true";
  condition
    ? Object.defineProperty(a, "b", {
        value: 5,
        enumerable: true,
      })
    : (a = {});

  condition
    ? Object.defineProperties(b, {
        c: {
          value: 5,
          enumerable: true,
        },
        d: {
          value: 6,
          enumerable: true,
        },
        e: {
          value: 7,
          enumerable: true,
        },
      })
    : (b = {});

  outputSingle.innerText = JSON.stringify(a);
  outputMultiple.innerText = JSON.stringify(b);
};
Condition:
<select id="condition">
  <option value="false">false</option>
  <option value="true">true</option>
</select>
<br/>
<br/>
Single Property: <pre id="outputSingle">{}</pre><br/>
Multiple Properties: <pre id="outputMultiple">{}</pre>

Related