4

This is something that I come up against quite often in Javascript. Let's say I have an object like this:

var acquaintances = {
   types: {
      friends: {
         billy: 6,
         jascinta: 44,
         john: 91
         others: ["Matt", "Phil", "Jenny", "Anna"]
      },
      coworkers: {
         matt: 1
      }
   }
}

In my theoretical program, all I know for sure is that acquaintances is an object; I have no idea whether acquaintances.types has been set, or whether friends has been set within it.

How can I efficiently check whether acquaintances.types.friends.others exists?

What I would normally do is:

if(acquaintances.types){
  if(aquaintances.types.friends){
    if(acquaintances.types.friends.others){
       // do stuff with the "others" array here
    } 
  }
}

Aside from being laborious, these nested if statements are a bit of a nightmare to manage (in practice my objects have far more levels than this!). But if I were to just try something like if(acquaintances.types.friends.others){) straight off the bat, and types hasn't been set yet, then the program will crash.

What ways does Javascript have of doing this in a neat, manageable way?

JVG
  • 20,198
  • 47
  • 132
  • 210
  • CoffeeScript has a `?` operator which fits your description, see [here](https://arcturo.github.io/library/coffeescript/02_syntax.html). Not aware of a simple trick in plain JS. – Jokester Aug 17 '15 at 03:57
  • 2
    BTW it is not more *efficient* in terms of execution time. – Jokester Aug 17 '15 at 04:02

4 Answers4

5

An alternative approach is:

((acquaintances.types || {}).friends || {}).others

which is shorter than other solutions, but may or may not thrill you.

You can also build a little helper to make the same idea a tiny bit more palatable:

function maybe(o) { return o || {}; }

Now you can do

maybe(maybe(acquaintances.types).friends).others

If you don't mind writing property names as strings, you could make a little helper:

function maybe(obj) {
  return Object.defineProperty(
    obj || {}, 
    'get', 
    { value: function(prop) { return maybe(obj[prop]); }
  );
}

Now you can write

maybe(acquaintances.types').get('friends').others

In ES6, you can do this, albeit clumsily, using destructuring assignment with defaults:

var { types: { friends: { others } = {} } = {} } = acquaintances;

If you want to use this in an expression context, instead of assigning to a variable, in theory you could use argument destructuring:

(({ types: { friends: { others } = {} } = {} }) => others)(acquaintances)

After all is said and done, the standard approach remains

acquaintances.types && 
  acquaintances.types.friends && 
  acquaintances.types.friends.others

This is why there is an active (?) discussion in the ES6 design groups about a CoffeeScript-like existential operator, but it does not seem to be converging very rapidly.

3

It's not nice in JavaScript.

You could add them to one big condition...

if (obj.prop && obj.prop.someOtherProp) { }

...or write a helper function where you pass an object and a string...

var isPropSet = function(object, propPath) {
    return !! propPath.split('.')
           .reduce(function(object, prop) { return object[prop] || {}; }, object);
};

isPropSet(obj, 'prop.someOtherProp);

...or you could use CoffeeScript and its ? operator...

obj.prop?.someOtherProp

You could also wrap the lookup in a try/catch, but I wouldn't recommend it.

alex
  • 479,566
  • 201
  • 878
  • 984
  • I think you need to `return object[prop] || {};`; otherwise a call such as `isPropSet(obj, 'prop.foo.bar')` will generate a run-time error. –  Aug 17 '15 at 05:38
  • @torazaburo Yes, you're right, it shouldn't blow up while testing for properties. – alex Aug 17 '15 at 07:31
3

The and operator is sequential so you can do this without nesting if statements.

if(acquaintances.types && aquaintances.types.friends && acquaintances.types.friends.others){
    //acquaintances.types.friends.others exists!
}
Henry Boldizsar
  • 489
  • 1
  • 8
  • 25
2

Instead of this:

if(acquaintances.types){
  if(aquaintances.types.friends){
    if(acquaintances.types.friends.others){
       // do stuff with the "others" array here
    } 
  }
}

Try this:

   if(acquaintances &&
      acquaintances.types &&
      acquaintances.types.friends &&
      acquaintances.types.friends.others) {
    }

Or

acquaintances &&
acquaintances.types &&
acquaintances.types.friends &&
acquaintances.types.friends.others ?
doSomething() : doSomethingElse()
peek4y
  • 31
  • 3