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

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

Answers:

Answer

In pure Javascript, I cannot think of anything more idiomatic than your first code snippet.

If, however, using the jQuery library is not out of the question, then $.extend() should meet your requirements because, as the documentation says:

Undefined properties are not copied.

Therefore, you can write:

var a = $.extend({}, {
    b: conditionB ? 5 : undefined,
    c: conditionC ? 5 : undefined,
    // and so on...
});

And obtain the results you expect (if conditionB is false, then b will not exist in a).

Answer

I think @InspiredJW did it with ES5, and as @trincot pointed out, using es6 is a better approach. But we can add a bit more sugar, by using the spread operator, and logical AND short circuit evaluation:

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

With EcmaScript2015 you can use Object.assign:

Object.assign(a, conditionB ? { b: 1 } : null,
                 conditionC ? { c: 2 } : null,
                 conditionD ? { d: 3 } : null);

var a, conditionB, conditionC, conditionD;
conditionC = true;
a = {};
Object.assign(a, conditionB ? { b: 1 } : null,
                 conditionC ? { c: 2 } : null,
                 conditionD ? { d: 3 } : null);

console.log(a);

Some remarks:

  • Object.assign modifies the first argument in-place, but it also returns the updated object: so you can use this method in a bigger expression that further manipulates the object.
  • Instead of null you could pass undefined or {}, with the same result. You could even provide 0 instead, because primitive values are wrapped, and Number has no own enumerable properties.

Even more concise

Taking the second point further, you could shorten it as follows (as @Jamie has pointed out), as falsy values have no own enumerable properties (false, 0, NaN, null, undefined, '', except document.all):

Object.assign(a, conditionB && { b: 1 },
                 conditionC && { c: 2 },
                 conditionD && { d: 3 });

var a, conditionB, conditionC, conditionD;
conditionC = "this is truthy";
conditionD = NaN; // falsy
a = {};
Object.assign(a, conditionB && { b: 1 },
                 conditionC && { c: 2 },
                 conditionD && { d: 3 });
console.log(a);

Answer

Using spread syntax with boolean (as suggested here) is not valid syntax. Spread can only be use with iterables.

I suggest the following:

const a = {
   ...(someCondition? {b: 5}: {} )
}
Answer
const obj = {
   ...(condition) && {someprop: propvalue},
   ...otherprops
}

Live Demo:

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

console.log(obj);

Answer

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.

Answer

If the goal is to have the object appear self-contained and be within one set of braces, you could try this:

var a = new function () {
    if (conditionB)
        this.b = 5;

    if (conditionC)
        this.c = 5;

    if (conditionD)
        this.d = 5;
};
Answer

If you wish to do this server side (without jquery), you can use lodash 4.3.0:

a = _.pickBy({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));

And this works using lodash 3.10.1

a = _.pick({ b: (someCondition? 5 : undefined) }, _.negate(_.isUndefined));
Answer

This has long been answered, but looking at other ideas I came up with some interesting derivative:

Assign undefined values to the same property and delete it afterwards

Create your object using an anonymous constructor and always assign undefined members to the same dummy member which you remove at the very end. This will give you a single line (not too complex I hope) per member + 1 additional line at the end.

var a = new function() {
    this.AlwaysPresent = 1;
    this[conditionA ? "a" : "undef"] = valueA;
    this[conditionB ? "b" : "undef"] = valueB;
    this[conditionC ? "c" : "undef"] = valueC;
    this[conditionD ? "d" : "undef"] = valueD;
    ...
    delete this.undef;
};
Answer
var a = {
    ...(condition ? {b: 1} : '') // if condition is true 'b' will be added.
}

I hope this is the much efficient way to add an entry based on the condition. For more info on how to conditionally add entries inside an object literals.

Answer

I would do this

var a = someCondition ? { b: 5 } : {};

Edited with one line code version

Answer

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
// }
Answer

I think your first approach to adding members conditionally is perfectly fine. I don't really agree with not wanting to have a member b of a with a value of undefined. It's simple enough to add an undefined check with usage of a for loop with the in operator. But anyways, you could easily write a function to filter out undefined members.

var filterUndefined = function(obj) {
  var ret = {};
  for (var key in obj) {
    var value = obj[key];
    if (obj.hasOwnProperty(key) && value !== undefined) {
      ret[key] = value;
    }
  }
  return ret;
};

var a = filterUndefined({
  b: (conditionB? 5 : undefined),
  c: (conditionC? 5 : undefined),
  d: (conditionD? 5 : undefined),
  e: (conditionE? 5 : undefined),
  f: (conditionF? 5 : undefined),
  g: (conditionG? 5 : undefined),
});

You could also use the delete operator to edit the object in place.

Answer

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

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;
// ...
Answer

Using lodash library, you can use _.merge

var a = _.merge({}, {
    b: conditionB ? 4 : undefined,
    c: conditionC ? 5 : undefined,
})
  1. If conditionB is false & conditionC is true, then a = { c: 5 }
  2. If both conditionB & conditionC are true, then a = { b: 4, c: 5 }
  3. If both conditionB & conditionC are false, then a = {}
Answer

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

more simplified,

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

Better answer:

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

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

Answer

Define a var by let and just assign new property

let msg = {
    to: "[email protected]",
    from: "[email protected]",
    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: "[email protected]",
    from: "[email protected]",
    subject: "Contact form",
    attachments: [
      {
        content: "attachment",
        filename: "filename",
        type: "mime_type",
        disposition: "attachment",
      },
    ]
}

In my opinion this is very simple and easy solution.

Answer

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

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.