18

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.

3
  • 2
    Outside of complying with the law of Demeter, no. Commented Oct 3, 2011 at 15:04
  • 1
    How I wish we had that operator in C#... Commented Oct 3, 2011 at 15:06
  • v8.dev/features/optional-chaining Commented Nov 18, 2019 at 5:24

8 Answers 8

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.

Sign up to request clarification or add additional context in comments.

7 Comments

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.
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: 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.
@Pointy var myVal = appData && foo && bar && settings won't produce the desired results. See my previous comment.
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
|
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.

Comments

4

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

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

Comments

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.

Comments

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.

Comments

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.

Comments

1
var product = ..., offering = (product||{}).offering, merchant = (offering||{}).merchant, merchantName = (merchant||{}).name; if (merchantName) displayMerchantName(merchantName); 

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

Comments

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 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.