JavaScript optional chaining - failsafe way of accessing object properties that might or might not exist.
One thing that makes JavaScript a very powerful programming language is its ability to create complex data structures in no time. I'm of course talking about JavaScript objects that you can define on the go without the need of creating generic classes and writing getters and setters for every property. But easy to construct often comes with easy to break. In this post we will look at a few different ways of working with JavaScript objects in a failsafe manner, so that we can access nested object properties when they exist but also prevent our program from crashing when they don't exist.
Let's first understand the problem (what not to do)
const product = {
id: 'id127781',
name: 'AAA batteries'
};
console.log(product.id); // id127781
console.log(product.price); // undefined
console.log(product.specification.weight); // Uncaught TypeError: Cannot read property 'weight' of undefined
In this example we are trying to access price which doesn't exist and will be returned as undefined. We are also trying to access "weight" which should exist under "specification", but for some reason doesn't for this product. This is not good because when trying to access a property on "undefined", the engine will trow an error like the comment shows. Let's try to prevent this!
Method 1 - "or empty object"
const product = {
id: 'id127781',
name: 'AAA batteries',
appearance: {
color: 'black'
}
};
const weight = ((product || {}).specification || {}).weight;
console.log(weight); // undefined
const color = ((product || {}).appearance || {}).color;
console.log(color); // black
// bonus: you can define fallback values very easy
const weight = ((product || {}).specification || { weight: 'N/A' }).weight;
console.log(weight); // N/A
This is one of my favorite ways of trying to access a deep leaf property in a data model where some properties are optional. Weight equals product or empty object dot specification or empty object dot weight.
In this case product is not undefined so we do not replace it with empty object. Now we are trying to access specification on product, but specification does not exist so we get an empty object. At last we are trying to access weight of empty object which doesn't exist so we get undefined. This way we prevent asking for a property on undefined and therefore also prevent the program from crashing.
Method 2 - ramda.js path
import * as R from 'ramda';
const weightUnit = R.path(['specification', 'weight', 'unit'], product);
console.log(weightUnit) // undefined
If you are able to work with npm libraries ramda.js comes in very handy for a lot of really solid JavaScript missing fundamentals. The path function is specifically made for this common problem of safely trying to access deep nested objects values. You can learn more about ramda and all their amazing functions at the documentation page: https://ramdajs.com/docs/#path
Method 3 - experimental optional chaining
If you have ever worked with programming languages like Swift or Kotlin, you have probably been using the ?. operator frequently? (which probably origins from some older programming languages, but since I am not a dinosaur, I know it from the newer stuff). According to mozilla and other sources this feature might exist in the world of JavaScript soon.
const weightUnit = product?.specification?.weight?.unit;
console.log(weightUnit); // undefined
IMPORTANT: this is not support yet in a variety of browsers! Currently it should be supported by Chrome v80+ and with the use of babel.
You can read more about this feature from reliable sources right here: