DEV Community

Dinesh Pandiyan
Dinesh Pandiyan

Posted on • Edited on

Accessing Nested Objects in JavaScript

undefined error in JavaScript

tldr; safely access nested objects in JavaScript in a super cool way.

JavaScript is amazing, we all know that already. But a few things in JavaScript are really weird and they make us scratch our heads a lot. One of those things is the confrontation with this error when you try to access a nested object,

Cannot read property 'foo' of undefined

Most of the times when we're working with JavaScript, we'll be dealing with nested objects and often we'll be needing to access the innermost nested values safely.

Let's take this nested object as an example.



const user = {
    id: 101,
    email: 'jack@dev.com',
    personalInfo: {
        name: 'Jack',
        address: {
            line1: 'westwish st',
            line2: 'washmasher',
            city: 'wallas',
            state: 'WX'
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

To access the name of the our user, we'll write



const name = user.personalInfo.name;
const userCity = user.personalInfo.address.city;


Enter fullscreen mode Exit fullscreen mode

This is easy and straight-forward.

But, for some reason, if our user's personal info is not available, the object structure will be like this,



const user = {
    id: 101,
    email: 'jack@dev.com'
}


Enter fullscreen mode Exit fullscreen mode

Now if you try you access the name, you'll be thrown Cannot read property 'name' of undefined.



const name = user.personalInfo.name; // Cannot read property 'name' of undefined


Enter fullscreen mode Exit fullscreen mode

This is because we're trying to access name key from an object that does not exist.

The usual way how most devs deal with this scenario is,



const name = user && user.personalInfo ? user.personalInfo.name : null;
// undefined error will NOT be thrown as we check for existence before access


Enter fullscreen mode Exit fullscreen mode

This is okay if your nested structure is simple, but if you have your data nested 5 or 6 levels deep, then your code will look really messy like this,



let city;
if (
    data && data.user && data.user.personalInfo &&
    data.user.personalInfo.addressDetails &&
    data.user.personalInfo.addressDetails.primaryAddress
   ) {
    city = data.user.personalInfo.addressDetails.primaryAddress;
}


Enter fullscreen mode Exit fullscreen mode

There are a few tricks to deal with this messy object structures.

Oliver Steele's Nested Object Access Pattern

This is my personal favorite as it makes the code look clean and simple. I picked this style from stackoverflow a while back and it is pretty catchy once you understand how it works.



const name = ((user || {}).personalInfo || {}).name;


Enter fullscreen mode Exit fullscreen mode

With this notation, you'll never run into Cannot read property 'name' of undefined. You basically check if user exists, if not, you create an empty object on the fly. This way, the next level key will always be accessed from an object that exists or an empty object, but never from undefined.

Unfortunately, you cannot access nested arrays with this trick

Access Nested Objects Using Array Reduce

Array reduce method is very powerful and it can be used to safely access nested objects.



const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) =>
        (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}

// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);

// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'addresses', 0, 'city']);
// this will return the city from the first address item.


Enter fullscreen mode Exit fullscreen mode

Typy

If you think the above methods are a lil' too mainstream, then you should try Typy library that I've written. In addition to safely accessing nested objects, it does many more awesome things. 🎉

It is available as an npm package - Typy

If you use Typy, your code will look like this,



import t from 'typy';

const name = t(user, 'personalInfo.name').safeObject;
const city = t(user, 'personalInfo.addresses[0].city').safeObject;
// address is an array


Enter fullscreen mode Exit fullscreen mode

Edit: There a few other libraries like Lodash and Ramda that can do this. But in light-weight front-end projects, especially if you're going to need only one or two methods from those libs, it's a good idea to opt for an alternative light-weight lib, or better, write your own.

Happy 'safely accessing nested objects in JavaScript'! 💥

Top comments (26)

Collapse
 
wesnetmo profile image
Wes
Collapse
 
wstone profile image
Will Stone

I have been looking forward to this a lot; checking the site on a near-weekly basis to see if it has progressed a stage

Collapse
 
flexdinesh profile image
Dinesh Pandiyan

It'll be great to see this operator soon in ES. I like the way how ES is progressing forward.

Collapse
 
martinhaeusler profile image
Martin Häusler

It's a GOOD thing that you get this error from JS! It tells you that something in your program and/or your data is wrong. By coding like this, you may circumvent the error message, but the root cause still exists. Use Typescript instead. It will tell you exactly when it is safe to navigate nested structures and when it is not. In the latter case, you should HANDLE the error, not ignore it.

Collapse
 
flexdinesh profile image
Dinesh Pandiyan

I agree TypeScript is safe and provides great many flexibilities in handling unexpected code.

But the alternate without a typescript (preference differs) would be to catch all these in a try catch block throughout the code, which will look really messy in a huge codebase. Sometimes, missing data in nested structures might be intentional too, mostly because JS is weakly typed. I think handling all those errors is a little too much effort and rather we should focusing on coding for the problem and let utils/libs handle the language shortcomings.

Collapse
 
itachiuchiha profile image
Itachi Uchiha

I use extract method for my project. Purpose can be different. These codes from my localization project on Github.

github.com/aligoren/local.js/blob/...

 extract(propertyName, object) {
        const parts = propertyName.split(".");
        let length = parts.length;
        let i;
        let property = object || this;

        for (i = 0; i < length; i++) {
            property = property[parts[i]];
        }

        return property;
    }

Usage:

const locals = {
    "tr": {
        "btn": {
            "welcome": {
                "text": "Merhaba hoÅŸgeldin"
            },
            "other": "DiÄŸeri"
        }
    },
    "en": {
        "btn": {
            "welcome": {
                "text": "Hi Welcome"
            },
            "other": "Other"
        }
    }
}

this.extract('btn.welcome.text', locals['tr'])
Collapse
 
flexdinesh profile image
Dinesh Pandiyan • Edited

This is great and easily readable. Maybe we should benchmark all the ways to access nested objects and spread the word so the community will know which one to use and when.

Collapse
 
carlosnufe profile image
Carlos Núñez

Take a look at i18n library, works in this way.

Collapse
 
xngwng profile image
Xing Wang

or get from Lodash.

Collapse
 
flexdinesh profile image
Dinesh Pandiyan • Edited

Lodash is all too awesome and there's nothing you can't do in Lodash. I'm a big fan of it. But sometimes, in a few light-weight front-end codebases, I find Lodash to be heavy and prefer to write the util on my own, especially if I'll be needing only one or two methods from Lodash.

Collapse
 
skyrpex profile image
Cristian Pallarés

You can install any lodash method in isolation for that case. If you're bundling your assets, you can install lodash and load just the parts you need.

Collapse
 
jerolan profile image
Jerome Olvera

Lodash get all the things 🙌🙌🙌

Collapse
 
alexkli profile image
Alexander Klimetschek

Here is a simpler self-contained helper function:

function resolve(obj, path) {
    return path.split('.').reduce((o, key) => o && o[key], obj);
}

resolve(user, 'personalInfo.name');
resolve(user, 'personalInfo.addresses.0.city');
Enter fullscreen mode Exit fullscreen mode

Note the dot notation, also works for arrays with index addressing.

Collapse
 
darrenvong profile image
Darren Vong

Some very nice solutions - I'm totally with you on writing your own util functions when it's small things, considering the impact from libraries like left-pad had when it was taken down! This will solve a lot of headache of nested property access until the optional/elvis operator becomes a thing in JS. Thanks for sharing!

Collapse
 
vadirn profile image
Vadim K. • Edited

What about this?


function grab(fn, defaultValue) {
  try {
    const value =  fn();
    return value;
  } catch (err) {
    return defaultValue;
  }
}

// and then

const city = grab(() => user.personalInfo.address.city);

Collapse
 
vadirn profile image
Vadim K.

Ah, default value doesn't make sense here, so

function grab(accessor) {
 try {
   const value = accessor();
   return value;
 } catch(err) {
   return;
 }
}
Collapse
 
kepta profile image
Kushan Joshi

Instead of reinventing wheel, I highly suggest looking at lenses randycoulman.com/blog/2016/07/12/t...

Collapse
 
flexdinesh profile image
Dinesh Pandiyan

I am a big fan of Lodash and have heard a great deal about Ramda too. But most of the time I find myself working on a small project that doesn't need 99% of the utils in these libraries, especially on the front-end. In those cases, I prefer to either write the util on my own or use a micro library that works for my project. But anyway thanks for letting me know about lenses. This looks pretty interesting and I'm going to take a closer look at how it works internally.

Collapse
 
brownieboy profile image
Michael Brown

import _get from "lodash/get";

Collapse
 
hrmny profile image
Leah

github.com/developit/dlv

How about something actually tiny?

Collapse
 
flexdinesh profile image
Dinesh Pandiyan

This is amazing. Concise and clean.

Do you want to be able to support arrays in string path? I can create a PR. But the code won't be as small though.

Collapse
 
zed_m profile image
Amine Hammou

thank you!