2

Often in a response from a remote API call, I receive nested objects:

var response = {
    data : {
        users : [
            {
                name : 'Mr. White'
            }
        ]
    }
}

I want to check whether the first user's name is 'Mr. White', and would naturally want to write something like.

var existed = response.data.users[0].name === 'Mr. White'

However I cannot be sure if all the objects are present, so to avoid exceptions instead I end up writing:

var existed = response && response.data && response.data.users && response.data.users[0].name === 'Mr. White'

Is there a nicer way to do this? Another ugly option that comes to mind is:

var existed = false;
try {
    var existed = response.data.users[0].name === 'Mr. White';
} catch(e) { }

In addition to vanilla javascript, I usually have underscore.js and jquery available too.

Edit:

Oops, noticed I asked a dupe of javascript test for existence of nested object key.

An interesting option based on those answers is:

var existed = (((response || {}).data || {}).users || [{}])[0].name === 'Mr. White';
Community
  • 1
  • 1
Bemmu
  • 17,849
  • 16
  • 76
  • 93
  • 3
    `try/catch` is the cleanest solution IMO. – Rory McCrossan Sep 05 '13 at 10:49
  • 1
    You might consider the [dotty.js](https://github.com/deoxxa/dotty) library. It is a small specialized library for fetching things out of nested objects. – Paul Sep 05 '13 at 11:03
  • Dotty looks interesting, but if I were reading the code I think it would confuse me more to see a library used for such a small purpose. – Bemmu Sep 05 '13 at 11:07

4 Answers4

1

I would use the try catch approach but wrap it in a function to hide the ugliness.

Jim Jeffries
  • 9,841
  • 15
  • 62
  • 103
1

You could hide this naughty try/catch block inside a function like this one :

function resolve(root, path){
    try {
        return (new Function(
            'root', 'return root.' + path + ';'
        ))(root);
    } catch (e) {}
}

var tree = { level1: [{ key: 'value' }] };
resolve(tree, 'level1[0].key'); // "value"
resolve(tree, 'level1[1].key'); // undefined

More on this : https://stackoverflow.com/a/18381564/1636522

Community
  • 1
  • 1
  • Selecting this as correct since it contained some new tricks I wasn't aware of. – Bemmu Sep 05 '13 at 12:17
  • One trick is inspired by jQuery which parses JSON strings using `new Function`. Note that you can customize the format of the path replacing the slash in `path.split('/')` with any char of your choice. –  Sep 05 '13 at 12:44
1

Instead of a try/catch, this should be done via checking whether each level in the object is defined or not.

go for

if(typeof(response)!="undefined" 
  && typeof(response.data)!="undefined"
  && typeof(response.data.users)!="undefined"
  && typeof(response.data.users[0])!="undefined"
  && typeof(response.data.users[0].name)!="undefined"
) {

    //executes only if response.data.users[0].name is existing

}
Mithun Satheesh
  • 27,240
  • 14
  • 77
  • 101
1

Here is a function which I used in one of my projects http://jsfiddle.net/JBBAJ/

var object = {
    data: {
        users: [
            {
                firstName: "White"
            },
            {
                firstName: "Black"
            }
        ]
    }
}
var read = function(path, obj) {
    var path = path.split(".");
    var item = path.shift();
    if(item.indexOf("]") == item.length-1) {
        // array
        item = item.split("[");
        var arrayName = item.shift();
        var arrayIndex = parseInt(item.shift().replace("]", ""));
        var arr = obj[arrayName || ""];
        if(arr && arr[arrayIndex]) {
            return read(path.join("."), arr[arrayIndex]);
        } else {
            return null;
        }
    } else {
        // object
        if(obj[item]) {
            if(path.length === 0) {
                return obj[item];
            } else {
                return read(path.join("."), obj[item]);
            }
        } else {
            return null;
        }
    }

}
console.log(read("data.users[0].firstName", object)); // White
console.log(read("data.users[1].firstName", object)); // Black
console.log(read("data.test.users[0]", object)); // null

The idea is to pass your path as a string along with your object. The idea was to prevent the throwing of an exception and receive just null as result of the path is wrong. The good thing is that the function works with every path and you don't need to write long if statements.

Krasimir
  • 13,306
  • 3
  • 40
  • 55