Access Javascript nested objects safely

I have json based data structure with objects containing nested objects. In order to access a particular data element I have been chaining references to object properties together. For example:

var a = b.c.d;

If b or b.c is undefined, this will fail with an error. However, I want to get a value if it exists otherwise just undefined. What is the best way to do this without having to check that every value in the chain exists?

I would like to keep this method as general as possible so I don't have to add huge numbers of helper methods like:

var a = b.getD();

or

var a = helpers.getDFromB(b);

I also want to try to avoid try/catch constructs as this isn't an error so using try/catch seems misplaced. Is that reasonable?

Any ideas?

Answers:

Answer

You can create a general method that access an element based on an array of property names that is interpreted as a path through the properties:

function getValue(data, path) {
    var i, len = path.length;
    for (i = 0; typeof data === 'object' && i < len; ++i) {
        data = data[path[i]];
    }
    return data;
}

Then you could call it with:

var a = getValue(b, ["c", "d"]);
Answer

Standard approach:

var a = b && b.c && b.c.d && b.c.d.e;

is quite fast but not too elegant (especially with longer property names).

Using functions to traverse JavaScipt object properties is neither efficient nor elegant.

Try this instead:

try { var a = b.c.d.e; } catch(e){}

in case you are certain that a was not previously used or

try { var a = b.c.d.e; } catch(e){ a = undefined; }

in case you may have assigned it before.

This is probably even faster that the first option.

Answer

probably it's may be simple:

let a = { a1: 11, b1: 12, c1: { d1: 13, e1: { g1: 14 }}}
console.log((a || {}).a2); => undefined
console.log(((a || {}).c1 || {}).d1) => 13

and so on.

Answer

Gets the value at path of object. If the resolved value is undefined, the defaultValue is returned in its place.

In ES6 we can get nested property from an Object like below code snippet.

const myObject = {
    a: {
      b: {
        c: {
          d: 'test'
        }
      }
    },
    c: {
      d: 'Test 2'
    }
  },

  isObject = obj => obj && typeof obj === 'object',

  hasKey = (obj, key) => key in obj;



function nestedObj(obj, property, callback) {
  return property.split('.').reduce((item, key) => {
    if (isObject(item) && hasKey(item, key)) {
      return item[key];
    }
    return typeof callback != undefined ? callback : undefined;
  }, obj);
}

console.log(nestedObj(myObject, 'a.b.c.d')); //return test


console.log(nestedObj(myObject, 'a.b.c.d.e')); //return undefined


console.log(nestedObj(myObject, 'c.d')); //return Test 2


console.log(nestedObj(myObject, 'd.d', false)); //return false


console.log(nestedObj(myObject, 'a.b')); //return {"c": {"d": "test"}}

Answer

An old question, and now days we have Typescript projects so often that this question seems irrelevant, but I got here searching for the same thing, so I made a simple function to do it. Your thoughts about not using try/catch is too strict for my taste, after all the seek for undefined.x will cause an error anyway. So with all that, this is my method.

function getSafe (obj, valuePath) {
    try { return eval("obj." + valuePath); } 
    catch (err) { return null; }
}

To use this we have to pass the object. I tried to avoid that, but there was not other way to get scope into it from another function (there is a whole bunch of questions about this in here). And a small test set to see what we get:

let outsideObject = {
    html: {
        pageOne: {
            pageTitle: 'Lorem Ipsum!'
        }
    }
};
function testme() {  
    let insideObject = { a: { b: 22 } };
    return {
        b: getSafe(insideObject, "a.b"),       // gives: 22
        e: getSafe(insideObject, "a.b.c.d.e"), // gives: null
        pageTitle: getSafe(outsideObject, "html.pageOne.pageTitle"),     // gives: Lorem Ipsum!
        notThere: getSafe(outsideObject, "html.pageOne.pageTitle.style") // gives: undefined
    }
}
testme(); 

UPDATE: Regarding the use of eval I think that eval is a tool to use carefully and not the devil itself. In this method, the user does not interfere with eval since it is the developer that is looking for a property by its name.

Answer

The answers here are good bare-metal solutions. However, if you just want to use a package that is tried and true, I recommend using lodash.

With ES6 you can run the following

import _ from 'lodash'

var myDeepObject = {...}

value = _.get(myDeepObject, 'maybe.these.path.exist', 'Default value if not exists')

Answer

This is an old question and now with es6 features, this problem can be solved more easily.

const idx = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);

Thanks to @sharifsbeat for this solution.

Answer

If you would like to have a dynamic access with irregular number of properties at hand, in ES6 you might easily do as follows;

function getNestedValue(o,...a){
  var val = o;
  for (var prop of a) val = typeof val === "object" &&
                                   val !== null     &&
                                   val[prop] !== void 0 ? val[prop]
                                                        : undefined;
  return val;
}
var obj = {a:{foo:{bar:null}}};
console.log(getNestedValue(obj,"a","foo","bar"));
console.log(getNestedValue(obj,"a","hop","baz"));

Answer

If you care about syntax, here's a cleaner version of Hosar's answer:

function safeAccess(path, object) {
  if (object) {
    return path.reduce(
      (accumulator, currentValue) => (accumulator && accumulator[currentValue] ? accumulator[currentValue] : null),
      object,
    );
  } else {
    return null;
  }
}
Answer
const getValue = (obj, property, defaultValue) => (
  property.split('.').reduce((item, key) => {
    if (item && typeof item === 'object' && key in item) {
      return item[key];
    }
    return defaultValue;
  }, obj)
)

const object = { 'a': { 'b': { 'c': 3 } } };

getValue(object, 'a.b.c'); // 3
getValue(object, 'a.b.x'); // undefined
getValue(object, 'a.b.x', 'default'); // 'default'
getValue(object, 'a.x.c'); // undefined
Answer

I will just paste the function that I use in almost all project as utility for this type of situation.

public static is(fn: Function, dv: any) {
    try {
        if (fn()) {
                return fn()
            } else {
                return dv
            }
        } catch (e) {
            return dv
        }
    }

So first argument is callback and second is the default value if it fails to extract the data due to some error.

I call it at all places as follows:

var a = is(()=> a.b.c, null);

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.