Making Deep Property Access Safe in JavaScript

Originally published in the A Drip of JavaScript newsletter.

If you've been working with JavaScript for any length of time, you've probably run across the dreaded TypeError: Cannot read property 'someprop' of undefined and the similar error for null.

var rels = {
    Viola: {
        Orsino: {
            Olivia: {
                Cesario: null
            }
        }
    }
};

// Outputs: undefined
console.log(rels.Viola.Harry);

// TypeError: Cannot read property 'Sally' of undefined
console.log(rels.Viola.Harry.Sally);

The problem, of course, is that a TypeError immediately halts execution of your code. It's simple to deal with when you have predictable inputs, but when you need to access a deep object property that may or may not be there it can be quite problematic.

Sometimes you can solve this by merging with a default object, but at other times that doesn't make sense.

Often, what we really want is to be able to ask for a deep property and just find out whether it has a proper value. If the deep property's parent or grandparent is undefined, then for our purposes the property can be considered undefined as well.

Let's take a look at a solution:

function deepGet (obj, properties) {
    // If we have reached an undefined/null property
    // then stop executing and return undefined.
    if (obj === undefined || obj === null) {
        return;
    }

    // If the path array has no more elements, we've reached
    // the intended property and return its value.
    if (properties.length === 0) {
        return obj;
    }

    // Prepare our found property and path array for recursion
    var foundSoFar = obj[properties[0]];
    var remainingProperties = properties.slice(1);

    return deepGet(foundSoFar, remainingProperties);
}

The deepGet function will recursively search a given object until it reaches an undefined or null property, or until it reaches the final property specified in the properties array.

Let's try it out.

// Outputs: { Cesario: null }
console.log(deepGet(rels, ["Viola", "Orsino", "Olivia"]));

// Outputs: undefined
console.log(deepGet(rels, ["Viola", "Harry"]));

// Outputs: undefined
console.log(deepGet(rels, ["Viola", "Harry", "Sally"]));

Excellent!

Of course, we probably want to use this value in some way. And it's unlikely that undefined in itself will be all that useful.

var oliviaRel = deepGet(rels, ["Viola", "Orsino", "Olivia"]);
var sallyRel = deepGet(rels, ["Viola", "Harry", "Sally"]);

// Produces a pretty graph of Olivia's love interest
graph(oliviaRel);

// Tries to produce a graph of Sally's love interest
graph(sallyRel);

The problem here is that we have to explicitly handle undefined in our graph function. But what if we are using a third party library that doesn't check for undefined? We could use the "or" trick, like so:

graph(sallyRel || {});

But that's not very explicit about our intentions, and will also fail if sallyRel happens to be false or another falsy value like 0 or "".

Alternately, we could explicitly check for null and undefined.

if (sallyRel === undefined || sallyRel === null) {
    sallyRel = {};
}

graph(sallyRel);

But that seems unnecessarily verbose.

It would be much nicer if we could just specify a default value to return instead of undefined. So how would we do that?

function deepGet (obj, props, defaultValue) {
    // If we have reached an undefined/null property
    // then stop executing and return the default value.
    // If no default was provided it will be undefined.
    if (obj === undefined || obj === null) {
        return defaultValue;
    }

    // If the path array has no more elements, we've reached
    // the intended property and return its value
    if (props.length === 0) {
        return obj;
    }

    // Prepare our found property and path array for recursion
    var foundSoFar = obj[props[0]];
    var remainingProps = props.slice(1);

    return deepGet(foundSoFar, remainingProps, defaultValue);
}

sallyRel = deepGet(rels, ["Viola", "Harry", "Sally"], {});

// Will output a graph based on the empty object
graph(sallyRel);

Now we have a nice safe way to do deep property access and even get back a useful value when the property doesn't have one.

If you find this utility useful or interesting, I have open-sourced it on GitHub. I've even added some syntactic sugar so you can use a string-based property list, like Viola.Harry.Sally.

Have ideas for future drips? Is there some part of JavaScript that consistently gives you trouble? Drop me a topic suggestion.

Thanks for reading!

Joshua Clanton

Want to improve your JavaScript skills?
Subscribe to A Drip of JavaScript for biweekly tips to help you level up as a JS developer.