Replacement for optional chaining for nested JS objects

Sometimes you have to get a variable from a nested object like this

const customer = {
  sources: {
    data: [{
      type: 'card',
      last4: '1234'
    }]
  }
}

If you want to get the value 1234, you could do this:

const { last4 } = customer.sources.data[0]

But if you don’t know if the whole object will be there (like with this Stripe response), you’ll need to check for every variable:

let last4
if (customer
  && customer.sources
  && customer.sources.data
  && customer.sources.data[0]
  && customer.sources.data[0].last4) {
    last4 = customer.sources.data[0].last4
}

Or you could do a try...catch:

let last4
try {
  last4 = customer.sources.data[0].last4
} catch (error) {
  // do nothing
}

But I like to have a little function that does this for me:

// Copy this function
const get = (object, selector, defaultValue = undefined) => {
  if (typeof object !== 'object' || object === null || typeof selector !== 'string') return defaultValue
  const value = selector.replace(/\[/g, '.[').split('.').reduce((prev, curr) => {
    if ([undefined, null].indexOf(prev) > -1) return undefined
    if (curr.startsWith('[')) return prev[curr.slice(1, -1)]
    else return prev[curr]
  }, object)
  return (value === undefined) ? defaultValue : value
}

// So you can do
const { last4 } = get(customer, 'sources.data[0]')

There is a relevant proposal by tc39 (thanks @TehShrike).

Inspired by the Ember get-function

Happy coding! – Found a mistake or a typo? Please submit a PR to my GitHub-repo.

Get my next post right in your inbox via Blogsend
(I don't get your email address so I can't spam you)

Like this post? Follow @adriaanvrossum on Twitter