admin管理员组文章数量:1126443
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),
};
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),
};
Share
Improve this question
edited Nov 4, 2021 at 14:18
Audwin Oyong
2,5563 gold badges19 silver badges36 bronze badges
asked Jul 28, 2012 at 20:07
viebelviebel
20.6k10 gold badges52 silver badges86 bronze badges
11
|
Show 6 more comments
27 Answers
Reset to default 2301I 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})
}
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}: {})
}
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
).
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 passundefined
or{}
, with the same result. You could even provide0
instead, because primitive values are wrapped, andNumber
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);
SIMPLE ES6 SOLUTION
Single condition with (&) - if condition
const didIPassExam = true
const study = {
monday : 'writing',
tuesday : 'reading',
/* check conditionally and if true, then add wednesday to study */
...(didIPassExam && {wednesday : 'sleep happily'})
}
console.log(study)
Dual condition with (? :) - if-else condition
const score = 110
//const score = 10
const storage = {
a:10,
b:20,
...(score > 100 ? {c: 30} : {d:40})
}
console.log(storage)
Explanation
Let's say you have storage
object like this
const storage = {
a : 10,
b : 20,
}
and you would like to add a prop to this conditionally based on score
const score = 90
You would now like to add prop c:30
to storage
if score
is greater than 100
.
If score is less than 100
, then you want to add d:40
to storage
. You can do like this
const score = 110
const storage = {
a:10,
b:20,
...(score > 100 ? {c: 30} : {d:40})
}
The above code gives storage
as
{
a: 10,
b: 20,
c: 30
}
If score = 90
then you get storage
as
{
a: 10,
b: 20,
d: 40
}
Codepen example
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
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.
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
This is probably the shortest solution with ES6
console.log({
...true && {foo: 'bar'}
})
// Output: {foo:'bar'}
console.log({
...false && {foo: 'bar'}
})
// Output: {}
I would do this
var a = someCondition ? { b: 5 } : {};
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;
};
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
// }
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;
};
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));
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.
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);
To expand on the ES6 based answers, I created this utility Typescript functions, that make the usage (in my opinion) more readable and less like a magical formula, and the intention very clear, and has the correct types:
/**
* Creates a simple object with ot without the specified property and value, depending on the condition.
* Usage: When creating an object literal that needs to include a property only in some cases.
* Use it with the spread operator.
* @example
* const user = {
* username,
* // will include the property only if isInternalUser() returns true
* ...conditionalObjectProperty('password', isInternalUser(), () => getUserPassword())
* }
* @param propertyName
* @param condition
* @param valueCreator
*/
export function conditionalObjectProperty<P extends string, V> (propertyName: P, condition: boolean, valueCreator: () => V) {
return condition
? { [propertyName]: valueCreator() }
: {};
}
/**
* Specialized conditional property creator that creates an object containing a specified property
* only when its value is non-nullable.
* Use in object literals with the spread operator.
* @example
* const middleName: string|undefined = getMiddleName();
* const user = {
* userName,
* firstName,
* lastName,
* // will be included only if middleName is non-nullable
* ...optionalObjectProperty('middleName', middleName)
* }
* @param propertyName
* @param value
*/
export function optionalObjectProperty<P extends string, V> (propertyName: P, value: V) {
return conditionalObjectProperty(propertyName, value != null, () => value);
}
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.
Using lodash library, you can use _.merge
var a = _.merge({}, {
b: conditionB ? 4 : undefined,
c: conditionC ? 5 : undefined,
})
- If conditionB is
false
& conditionC istrue
, thena = { c: 5 }
- If both conditionB & conditionC are
true
, thena = { b: 4, c: 5 }
- If both conditionB & conditionC are
false
, thena = {}
You can also nest conditional properties
const showAge = true
const showId = false
const showUserDetails = true
const user = {
email: '[email protected]',
...(showUserDetails && {
name: 'david',
...(showAge && { age: 29 }),
...(showId && { id: 1 })
})
}
// { name: 'david', age: 29}
/**
* here showId is true
*/
const showAge = true
const showId = true
const showUserDetails = true
const user = {
email: '[email protected]',
...(showUserDetails && {
name: 'david',
...(showAge && { age: 29 }),
...(showId && { id: 1 })
})
}
// { name: 'david', age: 29, id: 1}
This last example is something you may never use, but it‘s a good excercise nonetheless to understand the technique.
const x = true
const y = false
const obj = {
foo: 'foo',
bar: [
...x ? [{ id: 1 }] : [],
...y ? [{ id: 2 }] : [],
{ id: 3 }
],
...(x && {
baz: [
...x ? ['a'] : [],
'b',
...y ? ['c'] : []
]
})
}
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))
If you want to create object without NULL values
userStatus = sessionStorage?.getItem('isUserLoggedIn');
const a = {
"nameOfWebsite": "stackoverflow",
...(userStatus && { "UserLogged": userStatus }),
};
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>
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.
本文标签: In JavaScripthow to conditionally add a member to an objectStack Overflow
版权声明:本文标题:In JavaScript, how to conditionally add a member to an object? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736665731a1946652.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
a.b
, retrievinga.b
would returnundefined
anyway. – Teemu Commented Jul 28, 2012 at 20:15in
operator is used. – user1106925 Commented Jul 28, 2012 at 20:17