DEV Community

loading...
Cover image for 🛡️ How to safely access deeply nested values in javascript?

🛡️ How to safely access deeply nested values in javascript?

Thomas Betous
Software Engineer @Doctolib • Passionate about Web & Cloud
Updated on ・4 min read

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 
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'.
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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!

Discussion (4)

Collapse
tbetous profile image
Thomas Betous Author

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 !

Collapse
pumuckelo profile image
pumuckelo

thank you !

Collapse
tbetous profile image
Thomas Betous Author

Thanks for your comment ! :)