239

Is there an operator that would allow the following logic (on line 4) to be expressed more succinctly?

const query = succeed => (succeed? {value: 4} : undefined);

let value = 3;
for (let x of [true, false]) {
  try { value = query(x).value; } catch {} // <-- Don´t assign if no .value
}
console.log(value); 

The output is 4. If there is no .value field in the response, I want to keep the old value / don´t want the assignment to execute.

Notes:
  • If value = query(x)?.value were used, it would assign undefined to value
  • There is also the ??= operator, however it isn´t useful here, value ??= ... would only assign if value is currently null/undefined
  • In CoffeeScript, value = query(x).value if query(x)?.value? achieves the desired behaviour without try/catch, although it's repetitive
  • value = query(x)?.value ?? value works but isn't conditional assignment, the assignment still happens, if we were setting an Object's property the setter would be called unnecessarily. It is also repetitive
  • This logic cannot be abstracted into a function i.e. value = smart(query(x), "value"), the assignment can´t be made conditional that way
Keywords?

null propagation, existence operator

7
  • 5
    @naomik This kind of null checking can be very useful for if statements where you're checking for a deeply nested property, e.g. if( obj?.nested?.property?.value ) instead of if( obj && obj.nested && obj.nested.property && obj.nested.property.value ) Commented Aug 22, 2015 at 19:55
  • @SeanWalsh if your objects are that deeply nested, or if your functions are digging that deeply in your objects, there's probably several other problems with your app as well. Commented Aug 22, 2015 at 23:49
  • 1
    compare var appConfig = loadConfig(config, process.env); connect(appConfig.database); to connect(config). You can pass a much simpler object to connect instead of passing the whole config object, you can use conf.username, conf.password instead of attempting something like config[process.env]?.database?.username, config[process.env]?.database?.password. Reference: Law of Demeter. Commented Aug 22, 2015 at 23:59
  • Also, if you do something like set defaults or sanitize properties (this could be done in loadConfig in the example above), you can make assumptions about the existence of properties and skip null checking in countless areas of your app. Commented Aug 22, 2015 at 23:59
  • 5
    @naomik As long as the language supports nesting objects, it's still a useful feature - regardless of what you or I think of the architecture of the app itself. As an aside, complex object graphs like this are very common in ORMs that are modeling a complex data model. Commented Aug 23, 2015 at 0:26

10 Answers 10

143

For some years now it is simply

a?.b?.c
a?.b?.c ?? "default"

Check "Can I Use" for compatibility: https://caniuse.com/mdn-javascript_operators_optional_chaining,mdn-javascript_operators_nullish_coalescing


Update (2022-01-13): Seems people are still finding this, here's the current story:

Update (2017-08-01): If you want to use an official plugin, you can try the alpha build of Babel 7 with the new transform. Your mileage may vary

https://www.npmjs.com/package/babel-plugin-transform-optional-chaining

Original:

A feature that accomplishes that is currently in stage 1: Optional Chaining.

https://github.com/tc39/proposal-optional-chaining

If you want to use it today, there is a Babel plugin that accomplishes that.

https://github.com/davidyaha/ecmascript-optionals-proposal

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

6 Comments

Do I understand correctly that that doesn't include conditional assignment though? street = user.address?.street would set street in any case?
Actually unfortunately I think you're right. Street I think would be assigned undefined. But at least it wouldn't throw on trying to access properties on undefined.
It also looks like you can you the operator on the left hand, and if it evals anything to undefined, the right hand isn't evaluated. The babel plugin may vary a bit, haven't tested it much myself yet.
Regarding conditional assignment on the left-hand-side of the =, looks like that isn't supported in the official spec currently. github.com/tc39/proposal-optional-chaining#not-supported
As of May 2020, looks like current browsers and Typescript have implemented this!
|
95

It's not as nice as the ?. operator, but to achieve a similar result you could do:

user && user.address && user.address.postcode

Since null and undefined are both falsy values (see this reference), the property after the && operator is only accessed if the precedent it not null or undefined.

Alternatively, you could write a function like this:

function _try(func, fallbackValue) {
    try {
        var value = func();
        return (value === null || value === undefined) ? fallbackValue : value;
    } catch (e) {
        return fallbackValue;
    }
}

Usage:

_try(() => user.address.postcode) // return postcode or undefined 

Or, with a fallback value:

_try(() => user.address.postcode, "none") // return postcode or a custom string

10 Comments

Right probably I should clarify the question, the main thing I was after was conditional assignment
just a corollary to that; to find the inverse safely, you can use !(user && user.address && user.address.postcode) :)
foo && foo.bar && foo.bar.quux ... in a large codebase this is ugly and adds a lot of complexity that you would be better to avoid.
This is the cleanest solution I've found on the internet. I use this with typescript: _get<T>(func: () => T, fallbackValue?: T) :T
If user.address.postcode is undefined, _try(() => user.address.postcode, "") will return undefined instead of "". So the code _try(() => user.address.postcode, "").length will raise an exception.
|
77

2020 Solution, ?. and ??

You can now directly use ?. (Optional Chaining) inline to safely test for existence. All modern browsers support it.

?? (Nullish Coalescing) can be used to set a default value if undefined or null.

aThing = possiblyNull ?? aThing
aThing = a?.b?.c ?? possiblyNullFallback ?? aThing

If a property exists, ?. proceeds to the next check, or returns the valid value. Any failure will immediately short-circuit and return undefined.

const example = {a: ["first", {b:3}, false]}

example?.a  // ["first", {b:3}, false]
example?.b  // undefined

example?.a?.[0]     // "first"
example?.a?.[1]?.a  // undefined
example?.a?.[1]?.b  // 3

domElement?.parentElement?.children?.[3]?.nextElementSibling

null?.()                // undefined
validFunction?.()       // result
(() => {return 1})?.()  // 1

To ensure a default defined value, you can use ??. If you require the first truthy value, you can use ||.

example?.c ?? "c"  // "c"
example?.c || "c"  // "c"

example?.a?.[2] ?? 2  // false
example?.a?.[2] || 2  // 2

If you do not check a case, the left-side property must exist. If not, it will throw an exception.

example?.First         // undefined
example?.First.Second  // Uncaught TypeError: Cannot read property 'Second' of undefined

?. Browser Support - 92%, Nov 2021

?? Browser Support - 92%

Mozilla Documentation

--

Logical nullish assignment, 2020+ solution

New operators are currently being added to the browsers, ??=, ||= and &&=. They don't do quite what you are looking for, but could lead to same result depending on the aim of your code.

NOTE: These are not common in public browser versions yet, but Babel should transpile well. Will update as availability changes.

??= checks if left side is undefined or null, short-circuiting if already defined. If not, the left side is assigned the right-side value. ||= and &&= are similar, but based on the || and && operators.

Basic Examples

let a          // undefined
let b = null
let c = false

a ??= true  // true
b ??= true  // true
c ??= true  // false

Object/Array Examples

let x = ["foo"]
let y = { foo: "fizz" }

x[0] ??= "bar"  // "foo"
x[1] ??= "bar"  // "bar"

y.foo ??= "buzz"  // "fizz"
y.bar ??= "buzz"  // "buzz"

x  // Array [ "foo", "bar" ]
y  // Object { foo: "fizz", bar: "buzz" }

Browser Support Nov 2021 - 90%

Mozilla Documentation

2 Comments

The question is also about conditional assignment
Added a few examples with ?. and ??, and a detailed upcoming solution that may work for your situation. Best current solution is probably to just do aThing = possiblyNull?.thing ?? aThing
35

No. You may use lodash#get or something like that for this in JavaScript.

4 Comments

Are there proposals to add it to ES7?
Haven't encountered any.
@PeterVarga: Many. Too much discussion. Grammar is not an easy thing for this feature
lodash.get is slightly different in that obviusly it can't do conditional assignment.
21

Vanilla alternative for safe property access

(((a.b || {}).c || {}).d || {}).e

The most concise conditional assignment would probably be this

try { b = a.b.c.d.e } catch(e) {}

4 Comments

So how would you write the assignment in the question using this mechanism? Don't you still need a conditional to not assign in case the possiblyNull is not defined/null?
Sure, you still need to check if you want an assignment to happen or not. If you use '=' operator, something will inevitably be assigned, be it a data, an undefined or null. So the above is just a safe property access. Does conditional assignment operator even exists, in any language?
Of course, for example CoffeeScript, it also has ||=
+1 Even my first thought was (((a.b || {}).c || {}).d || {}).e. But more precise it would be ((((a || {}).b || {}).c || {}).d || {}).e
5

No, there is no null propagation operator in ES6. You will have to go with one of the known patterns.

You may be able to use destructuring, though:

({thing: aThing} = possiblyNull);

There are many discussions (e.g. this) to add such an operator in ES7, but none really took off until several years later when optional chaining syntax was standardised in ES2020.

4 Comments

That looked promising, however at least what Babel does with it is not any different from just aThing = possiblyNull.thing
@PeterVarga: Oops, you're right, destructuring works when the property is not existent, but not when the object is null. You'd have to supply a default value, pretty much like this pattern but with more confusing syntax.
This answer is dated - the null safe operators were officially added to ES6 ~10 months ago, and most browsers supported it for ~a year before it was official.
@ArtOfWarfare They were not added to ES6 (ES2015) but to ES2020, so the answer is still factually correct and answering the question, which asked about ES6 only. Updated anyway :-)
3

Going by the list here, there is currently no proposal to add safe traversal to Ecmascript. So not only is there no nice way to do this, but it is not going to be added in the forseeable future.

Edit: Since I originally made this post, it was in fact added to the language.

Comments

1
// Typescript
static nullsafe<T, R>(instance: T, func: (T) => R): R {
    return func(instance)
}

// Javascript
function nullsafe(instance, func) {
    return func(instance);
};

// use like this
const instance = getSomething();
let thing = nullsafe(instance, t => t.thing0.thing1.thingx);

Comments

0

A safe deep get method seems like a natural fit for underscore.js but there the issue is avoiding string programming. Modifying @Felipe's answer to avoid string programming (or at least pushes edge cases back to the caller):

function safeGet(obj, props) {
   return (props.length==1) ? obj[keys[0]] :safeGet(obj[props[0]], props.slice(1))
}

Example:

var test = { 
  a: { 
    b: 'b property value',
    c: { }
  } 
}
safeGet(test, ['a', 'b']) 
safeGet(test, "a.b".split('.'))  

4 Comments

To paraphrase Rick: this just sounds like string programming with extra steps.
lodash now implements deep get/set _.get(obj, array_or_dotstring)
But to be fair even the Javascript dot and string accessor notation is basically string programming, obj.a.b.c vs obj['a']['b']['c']
Where is keys coming from?
-5

I thought this question needed a bit of a refresh for 2018. This can be done nicely without any libraries using Object.defineProperty() and can be used as follows:

myVariable.safeGet('propA.propB.propC');

I consider this safe (and js-ethical) because of the writeable and enumerable definitions now available for the defineProperty method of Object, as documented in MDN

function definition below:

Object.defineProperty(Object.prototype, 'safeGet', { 
    enumerable: false,
    writable: false,
    value: function(p) {
        return p.split('.').reduce((acc, k) => {
            if (acc && k in acc) return acc[k];
            return undefined;
        }, this);
    }
});

I've put together a jsBin with console output to demonstrate this. Note that in the jsBin version I've also added a custom exception for empty values. This is optional, and so I've left it out of the minimal definition above.

Improvements are welcomed

1 Comment

With this you are de facto writing code into strings. It's really a bad idea to do that. It makes your code impossible to refactor and not IDE-friendly.

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.