133

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

let emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute // => defaultValue
sandstrom
  • 14,554
  • 7
  • 65
  • 62

20 Answers20

177

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.

sandstrom
  • 14,554
  • 7
  • 65
  • 62
  • If you are planning to support Internet Explorer (before Edge), you're out of luck: http://caniuse.com/#search=proxy Also, polyfill https://github.com/tvcutsem/harmony-reflect doesn't support IE – Tiagojdferreira Oct 18 '16 at 13:50
  • 47
    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) – Ziggy Oct 27 '17 at 01:05
  • 2
    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 //=> 42 – Vlad V Jan 17 '18 at 14:36
  • So using Proxy also means `Object.getEntries` can't be called on a Proxy :( – Meredith Nov 20 '18 at 14:44
  • 1
    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];`. – dosentmatter Jan 26 '19 at 06:16
  • 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 pollyfilled – Atanas Kovachev Jun 05 '20 at 06:58
  • 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. – Adam Mar 23 '21 at 11:37
65

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
alexdriedger
  • 2,884
  • 2
  • 23
  • 30
  • 3
    What is this code doing and what has it got to do with default values? – Yan King Yin Nov 07 '22 at 03:27
  • 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. – javajosh Mar 03 '23 at 18:49
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";
Community
  • 1
  • 1
nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • 10
    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`) – Shanimal May 11 '12 at 15:55
  • 5
    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. – nrabinowitz May 12 '12 at 00:02
  • 6
    @Shanimal 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 ;-) – Metalskin Feb 07 '13 at 08:35
  • yes @Metalskin we wouldn't care for the undefined value would we? lol. sorry, hopefully the mistake didn't cost you too much time :) – Shanimal Feb 07 '13 at 21:47
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)
}
Kiyan
  • 2,155
  • 15
  • 15
  • 3
    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, ); ...` – Mike Finch Apr 13 '18 at 18:38
  • And what if 's' will be undefined (i.e. not specified at all)? – Didzis Jan 11 '21 at 11:54
  • @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 || 100` – Kerwin Sneijders Apr 21 '21 at 15:53
  • what if s.top is zero, then what's the result in that case – Yash Kumar Verma Aug 02 '21 at 00:00
16

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

joaquin keller
  • 199
  • 1
  • 5
  • 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! – DaniCE Feb 26 '20 at 16:31
14

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" }
Francisco Puga
  • 23,869
  • 5
  • 48
  • 64
Colin
  • 320
  • 2
  • 9
  • 6
    Doing this you are also overwriting the values of `overwriteProperties` the correct way is: `const newObj = Object.assign({}, defaultProperties, overwriteProperties)` – Francisco Puga Aug 22 '19 at 10:29
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
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • if you call console.log(emptyObj), it return {}. not { attribute1 : 'defaultValue1' } – throrin19 Nov 14 '13 at 11:32
  • 2
    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. – jfriend00 Nov 14 '13 at 16:40
  • 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 problem – throrin19 Nov 15 '13 at 07:45
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

gianlucaparadise
  • 1,678
  • 20
  • 32
7

Or you can try this

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

val = dict['anotherkey'] || 'anotherval';
user1386350
  • 87
  • 1
  • 1
  • 17
    Bad idea if dict['anotherkey'] is 0. – Ray Toal Sep 14 '14 at 06:47
  • Maybe you should consider this: http://www.codereadability.com/javascript-default-parameters-with-or-operator/ – ebragaparah Nov 26 '15 at 16:52
  • @RayToal Then wouldn't `String()` fix that? As in: `val = dict[String('anotherkey')] || 'anotherval';` – Godstime Osarobo May 22 '19 at 22:11
  • 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. – Ray Toal May 22 '19 at 23:33
7

Simplest of all Solutions:

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

Now,

dict['last'] || 'Excluded'

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

Gil Baggio
  • 13,019
  • 3
  • 48
  • 37
  • 3
    this fails if you have a slightly different dict: `dict = {'first': 0, 'second': 1, 'third': 2}` – Vlad V Jun 18 '18 at 13:37
  • 2
    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"` – hobberwickey May 11 '19 at 22:58
  • 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` :-) –  Feb 06 '22 at 10:02
4

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
}};
Flame
  • 6,663
  • 3
  • 33
  • 53
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.

Alan Dong
  • 3,981
  • 38
  • 35
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?

James Long
  • 4,629
  • 1
  • 20
  • 30
  • 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). – sandstrom Jul 07 '11 at 15:42
  • This could be an answer in the future :) Let's hope browsers will get there soon :) ♬ – jave.web Mar 17 '15 at 00:59
  • 1
    I believe the future answer will be to use a [Proxy](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy) – James Long Apr 15 '15 at 16:05
  • @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. – sandstrom Apr 18 '15 at 23:04
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.

YoYo
  • 9,157
  • 8
  • 57
  • 74
  • 1
    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') – loop Feb 23 '21 at 10:47
  • 1
    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) } – loop Feb 23 '21 at 10:48
  • 1
    @loop I was really surprised just now by the unequal support across browsers. But the operator is supported as of nodejs 15 onward. – YoYo Feb 23 '21 at 17:47
  • For the record: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment https://github.com/tc39/proposal-logical-assignment – loop Feb 27 '21 at 17:40
  • 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. – loop Feb 27 '21 at 17:47
  • I found a suitable old question where I could elaborate on the topic: https://stackoverflow.com/questions/15944956/default-values-for-nested-javascript-hashes/66401897#66401897 – loop Feb 27 '21 at 18:09
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));
Nils
  • 2,041
  • 1
  • 15
  • 20
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
geekonaut
  • 5,714
  • 2
  • 28
  • 30
Vlad V
  • 109
  • 1
  • 6
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")
bramhaag
  • 628
  • 1
  • 9
  • 24
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/

Doug Amos
  • 4,243
  • 1
  • 22
  • 23
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.

C-F
  • 1,597
  • 16
  • 25
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
Chris Happy
  • 7,088
  • 2
  • 22
  • 49