HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Safe navigating function for nested object properties

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
propertiesfunctionnavigatingnestedforsafeobject

Problem

I've made a function to safely traverse deep objects.

var foo = {
    bar: {
        baz: 1
    }
};

function getDeepProp(obj, properties) {

    if (typeof properties === 'string')
        properties = properties.split('.');

    if (typeof obj === 'undefined')
        return;

    if (!properties.length)
        return obj;

    return getDeepProp(obj[properties[0]], properties.slice(1));
}

console.log(getDeepProp(foo, 'bar.baz')); // 1
console.log(getDeepProp(foo, 'baz.baz')); // undefined

// The function was created to avoid exceptions like:
console.log(foo.baz.baz); // Uncaught TypeError: Cannot read property 'baz' of undefined


Please suggest possible improvements.

http://jsfiddle.net/rtkczasa/

Solution

It is a good exercise on recursion, but I'd suggest using built-in functions. Think that what you want to abstract is this syntax:

obj[a][b][c]


But doing some checking in between:

obj[a] && obj[a][b] && obj[a][b][c]


It seems to be a good job for a reducer, where obj is the accumulator, that gets passed along, and the properties are given as an array that you can reduce over, so p below will be each of the properties above:

function getDeepProp(obj, props) {
  return props.split('.').reduce(function(acc, p) {
    // if the accumulator is something
    // then lookup the next nested property
    // otherwise return undefined
    if (acc == null) return
    return acc[p]
  },obj) // the initial accumulator is the object
}


For example:

var obj = {
  a: {
    b: {
      c: 1000
    }
  }
}

console.log(getDeepProp(obj, 'a.b.c')) //=> 1000
console.log(getDeepProp(obj, 'a.b.foo')) //=> undefined


Note that acc can be an object, or an array, since arrays are objects, and the bracket syntax is the same:

console.log(getDeepProp([[[2,[3]]]], '0.0.1.0')) //=> 3


Also, I would worry about the function doing too much. If properties have not been passed, then the function shouldn't be used in the first place. That's why I'd suggest, among other reasons, to take the object (the receiver of the operation), always last:

function getDeepProp(props, obj)

Code Snippets

obj[a][b][c]
obj[a] && obj[a][b] && obj[a][b][c]
function getDeepProp(obj, props) {
  return props.split('.').reduce(function(acc, p) {
    // if the accumulator is something
    // then lookup the next nested property
    // otherwise return undefined
    if (acc == null) return
    return acc[p]
  },obj) // the initial accumulator is the object
}
var obj = {
  a: {
    b: {
      c: 1000
    }
  }
}

console.log(getDeepProp(obj, 'a.b.c')) //=> 1000
console.log(getDeepProp(obj, 'a.b.foo')) //=> undefined
console.log(getDeepProp([[[2,[3]]]], '0.0.1.0')) //=> 3

Context

StackExchange Code Review Q#72253, answer score: 8

Revisions (0)

No revisions yet.