17

Is there an easy way to natively determine if a deep property exists within an object in JavaScript? For example, I need to access a property like this:

var myVal = appData.foo.bar.setting;

But there is a chance that either foo, foo.bar, or foo.bar.setting has not been defined yet. In Groovy, we can do something like this:

def myVal = appData?.foo?.bar?.setting

Is there a similar way to do this in JavaScript, without having to write a custom function or nested if statements? I've found this answer to be useful, but was hoping there was a more elegant and less custom way.

Damjan Pavlica
  • 31,277
  • 10
  • 71
  • 76
A.J. Brown
  • 913
  • 7
  • 14

8 Answers8

16

I find this very convenient:

var myVal = (myVal=appData) && (myVal=myVal.foo) && (myVal=myVal.bar) && myVal.settings;

If a property exists, the next part of the sequence will be attempted.

When the expression before && evaluates to false, the next part of the expression will not be checked. If either of myVal.appData.foo.bar.settings is not defined, the value of myVal (undefined( will evaluate to false.

Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Note that this approach is very useful for deeply nested variables. When the variable consists of two names, such as `foo.bar`, `myVal = foo && foo.bar` is more convenient. – Rob W Oct 03 '11 at 15:18
  • There's no reason for those "sub-assignments" inside the parentheses. JavaScript returns the actual value of the last expression of the sequence, or else `false`. It does not coerce the values to boolean like other languages with similar short-circuiting operators. – Pointy Oct 03 '11 at 15:20
  • @Pointy: the subassignments are to avoid repeating property names so that the overall code length is O(N) instead of O(N^2). I myself would try switching to a custom function by the time the length would start mattering though. – hugomg Oct 03 '11 at 15:26
  • @Pointy `var myVal = appData && foo && bar && settings` won't produce the desired results. See my previous comment. – Rob W Oct 03 '11 at 15:27
  • 1
    No, but I think pointy means: `var myVal = appData && appData.foo && appData.foo.bar && appData.foo.bar.settings;`. If the var names are not long, I prefer that way, actually. But if they are long, I prefer your way, Rob. Either way, I like it. +1 – uɥƃnɐʌuop Oct 03 '11 at 15:30
  • Ah I see what you mean; durrr. OK it makes sense to me now (though I'm not sure I'd do it that way). Sorry for being blind and dense :-) – Pointy Oct 03 '11 at 15:32
  • And it becomes absolutely unreadable and error-prone. Especially, when you encounter such construct for the first time. – Slava Fomin II Nov 10 '14 at 10:34
5

Sorry, it's not great:

var myVal = appData && appData.foo && appData.foo.bar && appData.foo.bar.setting;

Another option:

try {
    var myVal = appData.foo.bar.setting;
} catch (e) {
    var myVal = undefined;
}

The . operator is not really intended for accessing objects like this. Probably using a function would be a good idea.

robert
  • 5,093
  • 2
  • 20
  • 21
3

I find other approaches a bit immense. So, what would be the major drawback of the following approach:

// Pass the path as a string, parse it, and try to traverse the chain.
Object.prototype.pathExists = function(path) {
    var members = path.split(".");
    var currentMember = this;

    for (var i = 0; i < members.length; i++) {
        // Here we need to take special care of possible method 
        // calls and arrays, but I am too lazy to write it down.
        if (currentMember.hasOwnProperty(members[i])) {
            currentMember = currentMember[members[i]];   
        } else {
            return false;
        }
    }
    return true;
}

Basically, we define a method on the object (not necessarily) and that method takes the path to a nested object and returns existence confirmation, likeappData.pathExists("foo.bar.setting");

EDIT: Check object[prop] == undefined is not semantically correct since it will return false even if the property is defined although its value is undefined; that is why I use hasOwnProperty to check is the property defined. This might not be important if one needs to just fetch the value.

zpavlinovic
  • 1,507
  • 1
  • 17
  • 36
3

The optional chaining operator (?.) was introduced in ES2020. Now, you should be able to write:

const myVal = appData?.foo?.bar?.setting
Damjan Pavlica
  • 31,277
  • 10
  • 71
  • 76
2

If, after:

var myVal = appData.foo && appData.foo.bar && appData.foo.bar.setting;

myVal is not undefined, it will hold the value of appData.foo.bar.setting.

Yoshi
  • 54,081
  • 14
  • 89
  • 103
1
var product = ...,
    offering = (product||{}).offering,
    merchant = (offering||{}).merchant,
    merchantName = (merchant||{}).name;
if (merchantName)
   displayMerchantName(merchantName);

http://osteele.com/archives/2007/12/cheap-monads

Alex
  • 329
  • 2
  • 17
1

You can try this

var x = {y:{z:{a:'b'}}}
x && x.y && x.y.z && x.y.z.a //returns 'b'

This is not as good as the groovy expression but it works. The evaluation stops after encountering the first undefined variable.

Narendra Yadala
  • 9,554
  • 1
  • 28
  • 43
0

I just cooked this up so it might not work exactly right, I've also included two test cases.

function hasPropertyChain(o, properties) {

    var i = 0,
        currentPropertyChain = o;

    if(!o) {
        return false;
    }

    while(currentPropertyChain = currentPropertyChain[properties[i++]]);

    return i - 1 === properties.length;
}


alert(hasPropertyChain({a:{b:{c:'a'}}}, ['a','b','c'])); // true
alert(hasPropertyChain({a:{b:{c:'a'}}}, ['a','b','c', 'd'])); // false
anthony sottile
  • 61,815
  • 15
  • 148
  • 207