I would like to share a problem I had today and how I solved it. It is about the way to access deeply nested values in javascript. Here is a simple example :
const options = {
notification: {
enablePrivateMessage: true,
enableCommentResponse: false
}
}
const enablePrivateMessage = options.notification.enablePrivateMessage
const enableCommentResponse = options.notification.enableCommentResponse
What's the problem ?
This works fine, however what's happen if options
or notification
are undefined
?
const options = undefined
const enablePrivateMessage = options.notification.enablePrivateMessage
// Uncaught TypeError: Cannot read property 'notification' of undefined
const options = {}
const enablePrivateMessage = options.notification.enablePrivateMessage
// Uncaught TypeError: Cannot read property 'enablePrivateMessage' of undefined
Yep, that's embarrassing ! It's not safe to access deeply nested values if you're not sure that the intermediate values are set. In the previous example, I tried to access the property enablePrivateMessage
of notification
, but unfortunately notification
has not been set in options
and consequently is equal to undefined
. The error message tells me that I tried to access enablePrivateMessage
propery from something undefined
.
How to solve it ?
A first solution to this problem could be to browse nested properties one at a time and check if their values are null
or undefined
before accessing to the following nested value.
const options = {}
const enablePrivateMessage = options && options.notification && options.notification.enablePrivateMessage
// enablePrivateMessage == undefined
// alternative way
const enablePrivateMessage = !options ?
undefined : !options.notification ?
undefined : options.notification.enablePrivateMessage
// enablePrivateMessage == undefined
Although this works for simple cases, it could be painful to write that if your object is very depth. The code would be very long and difficult to read. Fortunately as the same way as lodash get
function, we can design a function to safely access properties. Here it is :
export const getPropValue = (object, path = '') =>
path.split('.')
.reduce((o, x) => o == undefined ? o : o[x]
, object)
const options = {}
const enablePrivateMessage = getPropValue(options, 'notification.enablePrivateMessage')
// enablePrivateMessage == undefined
How does it work ? getPropValue
is a function that takes two parameters. The first one is the object
to query and the second one is the path
to a nested prop we hope to find. When the function is executed, we split the path in an array in order to get all nested properties to browse.
// Example
// path = 'notification.enablePrivateMessage'
'notification.enablePrivateMessage'.split('.')
// ['notification', 'enablePrivateMessage']
Finally we execute a reduce
function from that array with an aggregator initially set with the object to query. The reduce
function browses all properties in the path and if one of these have an undefined value, then the result will be undefined
. Otherwise the value of the nested prop is returned.
// Example 1 : All properties are defined in the path
// object = {notification: {enablePrivateMessage: true}}
// path = 'notification.enablePrivateMessage'
['notification', 'enablePrivateMessage'].reduce((o, x) => {
console.log(o, x)
return o == undefined ? o : o[x]
}, {notification: {enablePrivateMessage: true}})
// {notification: {enablePrivateMessage: true}} 'notification'
// {enablePrivateMessage: true} 'enablePrivateMessage'
// true
// Example 2 : notification is undefined
// object = {}
// path = 'notification.enablePrivateMessage'
['notification', 'enablePrivateMessage'].reduce((o, x) => {
console.log(o, x)
return o == undefined ? o : o[x]
}, {})
// {} 'notification'
// undefined
What's about ES6 destructuring ?
Well, that's good ! But since ES6, destructuring feature is available in Javascript. This feature is really nice and allows developpers to easily declare and set variables with the nested properties of an object.
const options = {
notification: {
enablePrivateMessage: true,
enableCommentResponse: false
}
}
const {notification: {enablePrivateMessage, enableCommentResponse}} = options
console.log(enablePrivateMessage)
// true
console.log(enableCommentResponse)
// false
However, I cannot use my getPropValue
function in this case. If options
is undefined
, then the previous destructuring will result an error.
const options = undefined
const {notification: {enablePrivateMessage, enableCommentResponse}} = options
// Uncaught TypeError: Cannot destructure property `notification` of 'undefined' or 'null'.
A simple way to protect our code against that error is to set a fallback value if options
is undefined
. But it is not enough.
const options = undefined
const {notification: {enablePrivateMessage, enableCommentResponse}} = options || {}
// Uncaught TypeError: Cannot destructure property `enablePrivateMessage` of 'undefined' or 'null'
Once you did that, you need to set default values for all your nested properties before to get the wanted properties.
const options = undefined
let {notification: {enablePrivateMessage, enableCommentResponse} = {}} = options || {}
console.log(enablePrivateMessage)
// undefined
console.log(enableCommentResponse)
// undefined
This is not as convenient as getPropValue
function if the properties you want are very depth. In this case, you should consider that destructuring may not be the best way to access your properties because your instruction will be too long to be readable. In this case I recommand to access your properties one at a time with getPropValue
function.
Lodash can help you !
You should know that getPropValue
is a function I designed for this post. In real life I use the lodash get
function. It is really usefull in many cases and work likely getPropValue
. There is an extra parameter that allow you to set a fallback value if your path reach an undefined value before to get the targeted property.
import { get } from 'lodash'
const options = {
notification: {
enablePrivateMessage: true,
enableCommentResponse: false
}
}
// Example 1 : Simple case
get(options, 'notification.enablePrivateMessage')
// true
// Example 2 : Error case with 'fallback' as fallback value
get(options, 'toto.tata', 'fallback')
// 'fallback'
And that's it! You know all you need to know to safely access deep nested values in javascript! Hope you enjoyed my first post in dev.to!
Top comments (3)
It depends on what you want to do but I'd say in most cases I agree with you. Generally it's better to check data. I'd even say it's not a bad thing to let you code throws an exception because at least you can see that functionnaly there is something wrong with your code.
Thanks for your comment !
thank you !
Thanks for your comment ! :)