39

In Python, for a dictionary d,

d.setdefault('key', value)

sets d['key'] = value if 'key' was not in d, and otherwise leaves it as it is.

Is there a clean, idiomatic way to do this on a Javascript object, or does it require an if statement?

ptomato
  • 56,175
  • 13
  • 112
  • 165

7 Answers7

26
if (!('key' in d)) d.key = value;

or

'key' in d || (d.key = value);

(The last one uses the short-circuit behavior of conditional expressions.)


"d.key || (d.key = value)" suggested in the other answer is buggy: if an element exists but evaluates to false (false, null, 0, -0, "", undefined, NaN), it will be silently overwritten.

  • While this might be intended, it's most probably not intended for all of the aforementioned cases. So in such a case, check for the values separately:

    if (!('key' in d) || d.key === null) d.key = value;
    
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
bobtato
  • 1,157
  • 1
  • 9
  • 11
  • I would say "Don't use it if the value is a boolean". – roberkules Apr 19 '13 at 16:22
  • 2
    It's not just booleans though. `d.key` evaluates as false if it is any of: `["", 0, false, NaN, null, undefined]`, **or** if it actually isn't defined, so the test can fail if d is being used to store strings, numbers, booleans or objects. It's even possible to have a property which exists but holds the value `undefined` (because `d.key = undefined` is not the same as `delete d.key`). – bobtato Apr 19 '13 at 18:05
  • But it also depends on the usage. If you're building a setDefault function, I agree. If you just need to set a property of some options that you passed to a function, you know what type it is and can use it in an adequate way. – roberkules Apr 19 '13 at 21:23
25

Nowadays you can also do d.key ??= value

muodov
  • 573
  • 6
  • 8
  • 5
    amazing! thank you so much. May want to add a caveat that this works as long as entries are not allowed to legitimately by `undefined` or `null` (which should rarely be the case). And, as a bonus it is lazy, so an expression `??= expr` is not evaluated unnecessarily. I'm going to use this to replace python's `defaultdict` for when I only need to access a property in a single block of code e.g. `(keyToList[key] ??= []).push`. – ninjagecko Aug 11 '21 at 23:57
  • 2
    For reference, this is called "Nullish coalescing assignment". More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_assignment – Kerwin Sneijders Nov 12 '22 at 16:15
16

It's basically like using an if statement, but shorter:

d.key || (d.key = value);

Or

d.key = d.key || value;

Update: as @bobtato noted, if the property is already set to the value false it would overwrite it, so a better way would be:

!d.key && d.key !== false && (d.key = value);

Or, to do it as he suggested (just the shorthanded version):

'key' in d || (d.key = value);

// including overwriting null values:

('key' in d && d.key !== null) || (d.key = value);
roberkules
  • 6,557
  • 2
  • 44
  • 52
8

There is nothing built-in, but this function will do it:

function setDefault(obj, prop, deflt) {
  return obj.hasOwnProperty(prop) ? obj[prop] : (obj[prop] = deflt);
}

If obj will only contain non-falsy values, obj[prop] || (obj[prop] = deflt) is a fine simple alternative (but deflt will override anything falsy). This has the added advantage that deflt will only get evaluated when its value is needed, so it's OK for it to be an expensive expression.

DS.
  • 22,632
  • 6
  • 47
  • 54
6

It's not recommended and is never a good idea to augment the built-in classes. Instead, it's better to create your own implementations of built-in classes, a lot like the YUI library does (YArray, etc). However, I'm going to contradict that, because I often find that implementing useful short cuts like this are a benefit to writing clean maintainable code:

if (undefined === Object.prototype.setdefault) {
    Object.prototype.setdefault = function(key, def) {
        if (! this.hasOwnProperty(key)) {
            this[key] = def;
        }
        return this[key];
    };
}

So let's see it in action...

var a = {};

a.setdefault('mylist', []).push(1);
a.setdefault('mylist', []).push(2);

console.log(a.mylist.toString());
    1,2

As has been pointed out, it's never a good idea to employ thing = thing || defaultThing because it only tests non-typed equality of true, or thing == true. As opposed to typed equality: thing === true or thing !== undefined. So the only way to properly code setdefault in an inline way would be to use this snippet everywhere:

/* null == thing is equivalent to... */
if (undefined !== thing && null !== thing) {
    thing = defaultThing;
}
/* ...but it's not explicit and linters don't like it! */

But as I already said, this just adds bloat to your code and is prone to errors acquired from "copy-and-paste" syndrome.

Craig
  • 4,268
  • 4
  • 36
  • 53
2
a={}
key='3'
value = 2
a[key] = a[key] == undefined ? value : a[key]

## a = {'3': 2 }
RAM
  • 2,413
  • 1
  • 21
  • 33
  • 2
    A better test for undefined in Javascript is `typeof(a[key]) === "undefined"` and that will fail if you want to consider `null` values as empty which requires the additional test of `a[key] == null`. Since Javascript is a truthy language, both `undefined` and `null` are false and therefore a simple `a[key] || (a[key] = value)` is all that is necessary for this purpose and is quite common in practice. – Brandon Buck Apr 19 '13 at 15:38
0

Typescript version of @DS. 's answer:

export function setDefault<K extends string | number | symbol, T>(
  obj: Record<K, T>,
  prop: K,
  deflt: T,
) {
  // eslint-disable-next-line
  return obj.hasOwnProperty(prop) ? obj[prop] : (obj[prop] = deflt);
}

The line // eslint-disable-next-line aims to ignore the following eslint errors:

  9:3   error  Return statement should not contain assignment                             no-return-assign
  9:14  error  Do not access Object.prototype method 'hasOwnProperty' from target object  no-prototype-builtins
  9:50  error  Assignment to property of function parameter 'obj'                         no-param-reassign

If anyone knows how to improve the solution so that these errors are fixed instead of ignored, let me know.
I honestly don't see how it is possible to achieve this without assigning to the obj parameter.

Marco Castanho
  • 395
  • 1
  • 7
  • 24