Set default value of Javascript object attributes
Asked Answered
R

20

141

Is there a way to set the default attribute of a Javascript object such that:

let emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute // => defaultValue
Rai answered 6/7, 2011 at 17:59 Comment(3)
EVERY non existing attribute or just a KNOWN attribute name?Medici
let o = new Proxy({}, { get: (o, k) => k in o ? o[k] : 'some default value' }; console.log(o.key1)Ephebe
@ManoharReddyPoreddy your proposal deserve to be a proper answer. If JS do not provide default value for properties, Proxy is clearly the nicest approach !Valuer
R
184

Since I asked the question several years ago things have progressed nicely.

Proxies are part of ES6. The following example works in Chrome, Firefox, Safari and Edge:

let handler = {
  get: function(target, name) {
    return target.hasOwnProperty(name) ? target[name] : 42;
  }
};

let emptyObj = {};
let p = new Proxy(emptyObj, handler);

p.answerToTheUltimateQuestionOfLife; //=> 42

Read more in Mozilla's documentation on Proxies.

Rai answered 18/4, 2015 at 23:2 Comment(7)
If you are planning to support Internet Explorer (before Edge), you're out of luck: caniuse.com/#search=proxy Also, polyfill github.com/tvcutsem/harmony-reflect doesn't support IEHandily
Congrats, you've earned Phoenix (Gold Badge: answered one of your own questions at least a year later, with an answer that at least doubled the number of votes of the most popular answer)Oscitant
Object.withDefault=(defaultValue,o={})=>new Proxy(o,{get:(o,k)=>(k in o)?o[k]:defaultValue}); o=Object.withDefault(42); o.x //=> 42 o.x=10 o.x //=> 10 o.xx //=> 42Hesiod
So using Proxy also means Object.getEntries can't be called on a Proxy :(Wellbred
If you mean Object.entries, you can modify the handler to set properties when they are accessed. Change return target.hasOwnProperty(name) ? target[name] : 42; to if (!target.hasOwnProperty(name)) target[name] = 42; return target[name];.Blanding
That's what i was looking for. I remember i've read about this functionality in the past but i couldn't remember how it was used and what was the method. But yeah i think proxy is the most clean way of doing that. It will only cause issues if Internet Explorer is used but i guess it can be pollyfilledFatness
This answer is cool, but I think its not good to specify the default value in the handler. I think its more usefull to return null instead (or undefined) like this ` return target.hasOwnProperty(name) ? target[name] : null;` then you can assign stuff with default values like this: p.answerToTheUltimateQuestionOfLife ?? 42. This is usefull if you have a data object with multiple properties.Obscene
H
67

Use destructuring (new in ES6)

There is great documentation by Mozila as well as a fantastic blog post that explains the syntax better than I can.

To Answer Your Question

var emptyObj = {};
const { nonExistingAttribute = defaultValue } = emptyObj;
console.log(nonExistingAttribute); // defaultValue

Going Further

Can I rename this variable? Sure!

const { nonExistingAttribute: coolerName = 15} = emptyObj;
console.log(coolerName); // 15

What about nested data? Bring it on!

var nestedData = {
    name: 'Awesome Programmer',
    languages: [
        {
            name: 'javascript',
            proficiency: 4,
        }
    ],
    country: 'Canada',
};

var {name: realName, languages: [{name: languageName}]} = nestedData ;

console.log(realName); // Awesome Programmer
console.log(languageName); // javascript
Heathenism answered 8/9, 2017 at 9:12 Comment(2)
What is this code doing and what has it got to do with default values?Koenig
This is a really good answer with examples for setting defaults on missing keys, which is precisely what the OP asked for. To answer the question above, line 12 with var {name: realName... is only missing a default value, at most. You are being unfairly harsh to the author.Mcdougall
S
30

There isn't a way to set this in Javascript - returning undefined for non-existent properties is a part of the core Javascript spec. See the discussion for this similar question. As I suggested there, one approach (though I can't really recommend it) would be to define a global getProperty function:

function getProperty(o, prop) {
    if (o[prop] !== undefined) return o[prop];
    else return "my default";
}

var o = {
    foo: 1
};

getProperty(o, 'foo'); // 1
getProperty(o, 'bar'); // "my default"

But this would lead to a bunch of non-standard code that would be difficult for others to read, and it might have unintended consequences in areas where you'd expect or want an undefined value. Better to just check as you go:

var someVar = o.someVar || "my default";
Southdown answered 6/7, 2011 at 18:7 Comment(4)
warning: var someVar = o.someVar || "my default"; will have potentially unexpected results when o.someVar is populated but evaluates to false (e.g. null, 0, ""). someVar = o.someVar === undefined ? o.someVar : "my default"; would be better. I typically use || alone when the default also evaluates to false. (e.g. o.someVar || 0`)Rasla
That's a good point - this won't work anywhere where a false-y value is valid input, and you have to consider that when using this pattern. But more often than not, it makes sense to treat a property explicitly set to null or false in the same way as an unset property, in which case this does double duty. The warning is fair, though.Southdown
@Rasla Your example is the wrong way around, it should be someVar = o.someVar === undefined ? "my default" : o.someVar;, only a minor issue but it threw me for a little bit when I first tried your code ;-)Banket
yes @Banket we wouldn't care for the undefined value would we? lol. sorry, hopefully the mistake didn't cost you too much time :)Rasla
M
19

This seems to me the most simple and readable way of doing so:

let options = {name:"James"}
const default_options = {name:"John", surname:"Doe"}

options = Object.assign({}, default_options, options)

Object.assign() reference

Mechanician answered 5/10, 2019 at 4:1 Comment(2)
I was going to answer that underscore library has _.defaults(object, *defaults) method for that, but your answer get the same without any library!Myrlmyrle
This is now the solution I like best.Outbalance
W
17

my code is:

function(s){
    s = {
        top: s.top || 100,    // default value or s.top
        left: s.left || 300,  // default value or s.left
    }
    alert(s.top)
}
Wrier answered 3/2, 2014 at 16:37 Comment(4)
I like this solution the best, because it is similar to what I do in PHP. function foo( array $kwargs = array() ) { // Fill in defaults for optional keyworded arguments. $kwargs += array( 'show_user' => true, 'show_links' => false, ); ...Outbalance
And what if 's' will be undefined (i.e. not specified at all)?Regretful
@didzis You could do function(s = {}) which would fix the not filling it in at all. But you could also add this on the first line of the function: s = s || {} to fix not filling it and also fix null or undefined as parameters. You could also inline that, but I that's definitely not recommended: top: (s || {}).top || 100Mignonne
what if s.top is zero, then what's the result in that caseQueenie
J
15

The way I achieve this is with the object.assign function

const defaultProperties = { 'foo': 'bar', 'bar': 'foo' };
const overwriteProperties = { 'foo': 'foo' };
const newObj = Object.assign({}, defaultProperties, overwriteProperties);
console.log(defaultProperties);  // {"foo": "bar", "bar": "foo"}
console.log(overwriteProperties);  // { "foo": "foo" };
console.log(newObj);  // { "foo": "foo", "bar": "foo" }
Jackstraw answered 13/8, 2018 at 18:35 Comment(1)
Doing this you are also overwriting the values of overwriteProperties the correct way is: const newObj = Object.assign({}, defaultProperties, overwriteProperties)Integer
S
11

This sure sounds like the typical use of protoype-based objects:

// define a new type of object
var foo = function() {};  

// define a default attribute and value that all objects of this type will have
foo.prototype.attribute1 = "defaultValue1";  

// create a new object of my type
var emptyObj = new foo();
console.log(emptyObj.attribute1);       // outputs defaultValue1
Stemma answered 6/7, 2011 at 19:31 Comment(3)
if you call console.log(emptyObj), it return {}. not { attribute1 : 'defaultValue1' }Senile
Yes, because attribute1: defaultValue1 is on the prototype and console.log only enumerates items set on the top level object, not on the prototype. But, the value is there as my console.log(emptyObj.attribute1) shows.Stemma
it's right but it the same problem with JSON.stringify(emptyobj). I was forced to create a method that returns all the attributes in response to this problemSenile
L
8

Or you can try this

dict = {
 'somekey': 'somevalue'
};

val = dict['anotherkey'] || 'anotherval';
Larock answered 10/5, 2012 at 6:54 Comment(4)
Bad idea if dict['anotherkey'] is 0.Orientalize
Maybe you should consider this: codereadability.com/…Carmella
@RayToal Then wouldn't String() fix that? As in: val = dict[String('anotherkey')] || 'anotherval';Coalfish
No, sorry, it would not. Same problem. Try it: First do d = {x: 1, y: 0} then d['y'] || 100 and not it erroneously gives 100. Then try your idea which is d[String('y')] || 100 -- it still improperly gives 100 when it should give 0.Orientalize
I
8

I think the simplest approach is using Object.assign.

If you have this Class:

class MyHelper {
    constructor(options) {
        this.options = Object.assign({
            name: "John",
            surname: "Doe",
            birthDate: "1980-08-08"
        }, options);
    }
}

You can use it like this:

let helper = new MyHelper({ name: "Mark" });
console.log(helper.options.surname); // this will output "Doe"

Documentation (with polyfill): https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Ignition answered 5/7, 2017 at 14:16 Comment(0)
M
8

Simplest of all Solutions:

dict = {'first': 1,
        'second': 2,
        'third': 3}

Now,

dict['last'] || 'Excluded'

will return 'Excluded', which is the default value.

Monoceros answered 21/5, 2018 at 15:1 Comment(3)
this fails if you have a slightly different dict: dict = {'first': 0, 'second': 1, 'third': 2}Hesiod
This is also a great way to chain values that might not exist. For example a.b.c.d. If a, d, or c are undefined you hit an error, but you can just do (((a || {}).b || {}).c || {}).d) || "default"Brioche
This is great and the simplest. One improvement I've made is to put the default value inside the dict object itself, so dict[key] || dict.default :-)Abacist
O
5

If you only have an object that is a single level deep (nested object properties will not merge as expected since it directly destructures from the first level), you can use the following destructuring syntax:

const options = {
    somevar: 1234,
    admin: true
};

const defaults = {
    test: false,
    admin: false,
};

var mergedOptions = {...defaults, ...options};

Of which the output would be:

console.log(options);
// { somevar: 1234, admin: true }

console.log(mergedOptions);
// { test: false, admin: true, somevar: 1234 }

Or even formatted as a single statement (this is slightly unreadable though):

const options = {...{
    // Defaults
    test: false,
    admin: false,
}, ...{
    // Overrides
    somevar: 1234,
    admin: true
}};
Opprobrium answered 15/5, 2022 at 20:1 Comment(0)
S
2

I saw an article yesterday that mentions an Object.__noSuchMethod__ property: JavascriptTips I've not had a chance to play around with it, so I don't know about browser support, but maybe you could use that in some way?

Speos answered 6/7, 2011 at 18:9 Comment(4)
Saw that page too, we must have read the same HN article. Only methods though. It could be done with defineGetter but that isn't in ECMA5 unfortunately. They wen't with another getter/setter approach that is worse in my view (requires definition of properties beforehand).Rai
This could be an answer in the future :) Let's hope browsers will get there soon :) ♬Gimbals
I believe the future answer will be to use a ProxySpeos
@JamesLong yes, you are correct! turns out the future has arrived :) 3 years after I asked the question this now works in FF (and other browsers soon). I've added an answer below.Rai
U
2

I'm surprised nobody has mentioned ternary operator yet.

var emptyObj = {a:'123', b:'234', c:0};
var defaultValue = 'defaultValue';
var attr = 'someNonExistAttribute';
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue;//=> 'defaultValue'


attr = 'c'; // => 'c'
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue; // => 0

In this way, even if the value of 'c' is 0, it will still get the correct value.

Undressed answered 27/2, 2015 at 7:35 Comment(0)
K
2
var obj = {
  a: 2,
  b: 4
};

console.log(obj);

--> {a: 2, b: 4}

function applyDefaults(obj) {
  obj.a ||= 10;
  obj.b ||= 10;
  obj.c ||= 10;
}

// do some magic
applyDefaults(obj);

console.log(obj);

--> {a: 2, b: 4, c: 10}

This works because

undefined || "1111111" --> "1111111"
"0000000" || "1111111" --> "0000000"

as null, undefined, NaN, 0, "" (Empty String), false itself, are all considered to be equivalent to false (falsy). Anything else is true (truthy).

Note that this is not uniformly supported across browsers and nodejs versions (confirm for yourself).

So two troublesome cases are the empty String "" and 0 (zero). If it is important not to override those, you might need to rewrite this as:

if (typeof obj.d == "undefined") obj.d = "default"

This will be better supported across browsers also.

Alternatively you could write this as:

obj.d ??= "default"

This is the nullish assignment which applies only to values that are null or undefined (nullish) - of which the empty string is not part. However, this has again a diminished cross-browser support.

See also on the official Mozilla Website - Assigning a default value to a variable.

Kollwitz answered 12/2, 2021 at 3:47 Comment(6)
I find it beneficial that this logical OR assignment can be used to initialize a property and to subsequently refer to it also, nesting such references infinitely: ((document['myLog']||={}).lines||=[]).push('possibly first')Evora
Node just does not support the operator yet, so I had to work it around clumsily: function orAssign(o, key, def) { return o[key] || (o[key]=def) }Evora
@Evora I was really surprised just now by the unequal support across browsers. But the operator is supported as of nodejs 15 onward.Kollwitz
For the record: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… github.com/tc39/proposal-logical-assignmentEvora
I haven't seen anywhere else the usage I demonstrated above and I think this would deserve more attention. :( With that syntax one can easily and cleanly initialize arrays and maps on first assignment.Evora
I found a suitable old question where I could elaborate on the topic: #15945456Evora
H
1

This is actually possible to do with Object.create. It will not work for "non defined" properties. But for the ones that has been given a default value.

var defaults = {
    a: 'test1',
    b: 'test2'
};

Then when you create your properties object you do it with Object.create

properties = Object.create(defaults);

Now you will have two object where the first object is empty, but the prototype points to the defaults object. To test:

console.log('Unchanged', properties);
properties.a = 'updated';
console.log('Updated', properties);
console.log('Defaults', Object.getPrototypeOf(properties));
Hottentot answered 12/5, 2014 at 18:45 Comment(0)
H
1
Object.withDefault = (defaultValue,o={}) => {
  return new Proxy(o, {
    get: (o, k) => (k in o) ? o[k] : defaultValue 
  });
}

o = Object.withDefault(42);
o.x  //=> 42

o.x = 10
o.x  //=> 10
o.xx //=> 42
Hesiod answered 17/1, 2018 at 14:39 Comment(1)
So... using Proxies also means you lose out on all the nice Object methods now :(Wellbred
S
1

With the addition of the Logical nullish assignment operator, you can now do something like this

const obj = {}
obj.a ??= "default";

In the case where you have an empty list as the default value and want to push to it, you could do

const obj = {}
(obj.a ??= []).push("some value")
Swinford answered 15/6, 2022 at 9:9 Comment(0)
F
0

One approach would be to take a defaults object and merge it with the target object. The target object would override values in the defaults object.

jQuery has the .extend() method that does this. jQuery is not needed however as there are vanilla JS implementations such as can be found here:

http://gomakethings.com/vanilla-javascript-version-of-jquery-extend/

Foothold answered 27/5, 2016 at 6:24 Comment(0)
A
0

If you need to construct a copy of a given object with missing fields filled by default values, you can use spread syntax:

let a = {
    foo: "foo value",
    bar: "bar value"
};

let result = {
    foo: "default foo value",
    bar: "default bar value",
    bazz: "default bazz value",

    ... a
}

The input object should come after the defaults, so it will replace the defaults by the values of its properties if they are defined, but leave intact the default values of missing properties. So the result will be:

{
    foo: "foo value",
    bar: "bar value",
    bazz: "default bazz value"
}

Note that the default values will only appear for missing properties. If a property exists, but has a null or undefined value, it will not be replaced by a default value, so null-s and undefined-s will appear in the result.

Agrestic answered 3/4, 2023 at 2:7 Comment(0)
T
0

If using Node.js, you can use the popular object.defaults library:

Usage

var defaults = require('object.defaults');

var obj = {a: 'c'};
defaults(obj, {a: 'bbb', d: 'c'});
console.log(obj);
//=> {a: 'c', d: 'c'}

Or immutable defaulting:

var defaults = require('object.defaults/immutable');
var obj = {a: 'c'};
var defaulted = defaults(obj, {a: 'bbb', d: 'c'});
console.log(obj !== defaulted);
//=> true
Toddle answered 21/7, 2023 at 23:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.