129

a "problem" which i have every now and then is that i have an object e.g. user = {} and through the course of using the app this gets populated. Let's say somwhere, after an AJAX call or something i do this:

user.loc = {
    lat: 50,
    long: 9
}

At another place i want to check if user.loc.lat exists.

if (user.loc.lat) {
    // do something
}

If it does not exists, this will cause an error. If user.loc.lat is undefined, user.loc of course is undefined as well.

"Cannot read property 'lat' of null" - Dev Tools error

That means I need to check it like this:

if (user.loc) {
    if (user.loc.lat) {
        // do something
    }
}

or

if (user.loc && user.loc.lat) {
    // do something
}

This isn't really pretty and the bigger my objects are the worse it gets - obviously (imagine 10 levels of nesting). It kind bums me that if(user.loc.lat) isn't just returning false if user.loc is undefined as well.

What's the ideal way to check situations like this?

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
ProblemsOfSumit
  • 19,543
  • 9
  • 50
  • 61
  • 11
    Try this `if(user && user.loc && user.loc.lat) {` – Aamir Afridi May 22 '14 at 13:58
  • You can check value of null and undefined using [typeof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) – Aamir Afridi May 22 '14 at 14:00
  • 1
    @AamirAfridi—even though that's how the OP was written, the test should stop at the last object, not the property. e.g. the test will return false if `user.loc.lat` exists but has a falsey value (such as `0`). :-) – RobG May 22 '14 at 14:01

4 Answers4

171

You can use an utility function like this:

get = function(obj, key) {
    return key.split(".").reduce(function(o, x) {
        return (typeof o == "undefined" || o === null) ? o : o[x];
    }, obj);
}

Usage:

 get(user, 'loc.lat')     // 50
 get(user, 'loc.foo.bar') // undefined

Or, to check only if a property exists, without getting its value:

has = function(obj, key) {
    return key.split(".").every(function(x) {
        if(typeof obj != "object" || obj === null || ! x in obj)
            return false;
        obj = obj[x];
        return true;
    });
}

if(has(user, 'loc.lat')) ...
gog
  • 10,367
  • 2
  • 24
  • 38
  • 5
    The problem with the *get* approach is that it may return a property from anywhere in the chain, it doesn't specifically test for the last property. – RobG May 22 '14 at 14:21
  • 2
    @RobG How so? As soon as it hits an undefined that just gets passed all the way through and the result is undefined. – James Montagne May 22 '14 at 14:27
  • @RobG: Updated `has` for more accurate type checking, so that `has('foo.bar.baz')` won't fail if `foo.bar` is, say, a number. – gog May 22 '14 at 14:27
  • @JamesMontagne—`get({foo:{bar:{fum:undefined}}},'foo.fog')` returns *undefined* because *fog* isn't a property of *bar*, not because it read the value. Similarly , *has* should check for the existence of the property, not whether attempting to read the value returns *undefined* or *null*. It will also fail if *obj* is a function object or a host object where *typeof* doesn't return *object* (it isn't required to and doesn't in some browsers). – RobG May 23 '14 at 00:27
  • @RobG _"`get({foo:{bar:{fum:undefined}}},'foo.fog')` returns undefined because fog isn't a property of bar, not because it read the value."_ ....well since there is no value to read, that property will be undefined...that's the point. – kingPuppy Mar 24 '15 at 15:25
  • Your answer says "*to check only if a property exists*", but it doesn't, it checks the returned value, which is quite different. Also, due to operator precedence, `! x in obj` is evaluated as `(!x) in obj` so it's checking for a property name of "true" or "false", depending on the result of `!x` (which will always be *true* since it's a string), so the result of that expression will be *false* unless there is a property `obj.true`. I think you mean it to be `!(x in obj)`. – RobG Mar 24 '15 at 23:22
  • 1
    I wish lodash would and a function for this.`_.has` is the closest but doesn't check the value. – GFoley83 Jul 28 '16 at 02:55
  • 7
    Actually, scratch that, it does! Just use `_.get` http://stackoverflow.com/a/24046380/654708 – GFoley83 Jul 28 '16 at 03:05
  • This code has flaw it's not full prof right code is has = function(obj, key) { return key.split(".").every(function(x) { if(typeof obj != "object" || obj === null || !( x in obj)) return false; obj = obj[x]; return true; }); – Anupam Jun 01 '17 at 07:22
  • This article truly summarizes all the ways of doing this : https://hackernoon.com/accessing-nested-objects-in-javascript-f02f1bd6387f – Anamika Modi Mar 18 '19 at 06:31
  • How to default this to 0 if it's `undefined`? – sojim2 Aug 13 '19 at 21:48
  • It doesn't work for me unless I write `! x in obj` as `! (x in obj)`. – Random dude Jun 25 '20 at 11:44
  • it does not work for array item, like `a.b[0]` – Mohammad Arshad Alam Jul 28 '21 at 04:38
32

You can combine the checks using lazy and:

if(user.loc && user.loc.lat) { ...

Or, you use CoffeeScript. And ES2020 has new syntax ( Nullish coalescing Operator ).

user.loc?.lat?. '...'

which would run the checks for loc property and safeguard against empty objects.

punund
  • 4,321
  • 3
  • 34
  • 45
  • 2
    sorry, you were all very fast in answering. Your first approach is also a case that i want to avoid. Consider that i have huge object with 5 to 15 levels of nesting. – ProblemsOfSumit May 22 '14 at 14:05
  • 1
    You don't have a lot of options here. (1) property checking, manually, or with CoffeeScript, (2) try/catch. If you have an object with 15 levels of nesting and its property may be absent at any level, then I'd think you have some sub-optimal design patterns with you application. – punund May 22 '14 at 14:14
  • 1
    It's an example. I want a way to cover that scenario. I obviously would avoid 15 levels of nesting but what if an old server-side application just does it that way and i have to deal with it? That's why i wanted to cover that scenario – ProblemsOfSumit May 22 '14 at 14:15
  • 3
    `.?` is called the `optional chaining operator`. The nullish coalescing operator` (`??`) is very different and more similar to the OR operator (`||`). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator#relationship_with_the_optional_chaining_operator_. – Forivin Jul 15 '21 at 10:50
28

Well, javascript has try-catch. Depending on what you actually need to do (i.e. what your else statement would look like if it's undefined), that may be what you want.

example:

try {
   user.loc.lat.doSomething();
} catch(error) {
   //report
}
MarioDS
  • 12,895
  • 15
  • 65
  • 121
  • 9
    I don't think it's a good idea to throw exceptions instead of if-else, if you know that a var may be unset, I don't consider that an exception, thus you should handle it without throwing in a try-catch. (Of course you might throw in the else-clause) then. – Jonas Stensved Mar 13 '16 at 16:14
  • I like this solution for simple stuff that sometimes get harder to read with conditionals. @JonasStensved is pointing no using try catch on this because launching exception (you are not launching it until you write `throw`, i think) but the problem could be something else goes wrong in the block and you will not be able to detect it on the future. – pikilon Jun 09 '18 at 10:26
  • @pikilon Rule of thumb: Do not use exceptions for flow control. In your case something really is unexpected and an exception, then throw. Otherwise handle it as any other code condition. – Jonas Stensved Jun 14 '18 at 09:22
8

Try this if(user && user.loc && user.loc.lat) {...}

You can check value of null and undefined using typeof

If .loc has value false than you can try

if(user && user.loc && typeof(user.loc)!=="undefined"){...}

If you have a huge nested object than have a look at

Source.

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments),
      obj = args.shift();

  for (var i = 0; i < args.length; i++) {
    if (!obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

Update: Try lodash.get

Community
  • 1
  • 1
Aamir Afridi
  • 6,364
  • 3
  • 42
  • 42