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),
};
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
).
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})
}
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.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.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);
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}: {} )
}
const obj = {
...(condition) && {someprop: propvalue},
...otherprops
}
Live Demo:
const obj = {
...(true) && {someprop: 42},
...(false) && {nonprop: "foo"},
...({}) && {tricky: "hello"},
}
console.log(obj);
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.
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;
};
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));
This has long been answered, but looking at other ideas I came up with some interesting derivative:
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;
};
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.
I would do this
var a = someCondition ? { b: 5 } : {};
Edited with one line code version
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 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.
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))
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;
// ...
Using lodash library, you can use _.merge
var a = _.merge({}, {
b: conditionB ? 4 : undefined,
c: conditionC ? 5 : undefined,
})
false
& conditionC is true
, then a = { c: 5 }
true
, then a = { b: 4, c: 5 }
false
, then a = {}
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)
more simplified,
const a = {
...(condition && {b: 1}) // if condition is true 'b' will be added.
}
Better answer:
const a = {
...(someCondition ? {b: 5} : {})
}
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
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.
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);
©2020 All rights reserved.