147

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

let emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute // => defaultValue
3
  • EVERY non existing attribute or just a KNOWN attribute name? Commented Jul 6, 2011 at 18:10
  • 2
    let o = new Proxy({}, { get: (o, k) => k in o ? o[k] : 'some default value' }; console.log(o.key1) Commented Jun 24, 2021 at 17:40
  • @ManoharReddyPoreddy your proposal deserve to be a proper answer. If JS do not provide default value for properties, Proxy is clearly the nicest approach ! Commented Mar 1, 2022 at 11:16

21 Answers 21

190

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.

Sign up to request clarification or add additional context in comments.

8 Comments

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 IE
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)
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
So using Proxy also means Object.getEntries can't be called on a Proxy :(
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];.
|
71

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

3 Comments

What is this code doing and what has it got to do with default values?
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.
I agree with @YanKingYin This code may solve the problem, but it is not at all obvious what it is doing or why. It may be a good answer in the sense of being a good solution, but it is not a good answer in the sense of clearly explaining a solution.
31

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

4 Comments

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`)
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.
@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 ;-)
yes @Metalskin we wouldn't care for the undefined value would we? lol. sorry, hopefully the mistake didn't cost you too much time :)
21

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

2 Comments

I was going to answer that underscore library has _.defaults(object, *defaults) method for that, but your answer get the same without any library!
This is now the solution I like best.
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)
}

4 Comments

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, ); ...
And what if 's' will be undefined (i.e. not specified at all)?
@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
what if s.top is zero, then what's the result in that case
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" }

1 Comment

Doing this you are also overwriting the values of overwriteProperties the correct way is: const newObj = Object.assign({}, defaultProperties, overwriteProperties)
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

3 Comments

if you call console.log(emptyObj), it return {}. not { attribute1 : 'defaultValue1' }
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.
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
9

Simplest of all Solutions:

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

Now,

dict['last'] || 'Excluded'

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

3 Comments

this fails if you have a slightly different dict: dict = {'first': 0, 'second': 1, 'third': 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"
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 :-)
8

Or you can try this

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

val = dict['anotherkey'] || 'anotherval';

4 Comments

Bad idea if dict['anotherkey'] is 0.
Maybe you should consider this: codereadability.com/…
@RayToal Then wouldn't String() fix that? As in: val = dict[String('anotherkey')] || 'anotherval';
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.
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

Comments

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

Comments

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?

4 Comments

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).
This could be an answer in the future :) Let's hope browsers will get there soon :) ♬
I believe the future answer will be to use a Proxy
@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.
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.

Comments

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.

6 Comments

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')
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 I was really surprised just now by the unequal support across browsers. But the operator is supported as of nodejs 15 onward.
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.
|
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));

Comments

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

1 Comment

So... using Proxies also means you lose out on all the nice Object methods now :(
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")

Comments

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/

Comments

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.

Comments

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

Comments

0
let emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute; // => defaultValue

// You can use ?? operator
// It returns right operand if left is null or undefined
let result = emptyObj.nonExistingAttribute ?? 'Your default value'; 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.