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