4

I'm working in a large Javascript codebase at the moment littered with code resorting to Exceptions for flow control

function getChecklistUrl() {
    try {
        return dataLayerObject.config.checklist;
    } catch (e) {
        try {
            console.error('dataLayer', e);
        } catch (ignore) {}
    }
}

I might favor conditional logic such as this implementation of the same function

function getChecklistUrl() {
    if(typeof dataLayerObject == 'object'        &&
       'config' in dataLayerObject               &&
       typeof dataLayerObject.config == 'object' &&
       'checklist' in dataLayerObject.config     &&
       typeof dataLayerObject.config.checklist == 'object') {
        return dataLayerObject.config.checklist;
    }
    return null;
}

While the later feels longwinded, a helper function could certainly be written to reduce the boilerplate for such checks.

Is the former then idiomatic for Javascript? Is the later brittle (across browsers / scenarios) and better left to a try / catch anyway? Or is the former simply evidence of laziness?

Edit

these objects are presumed to be 'plain' objects, such as var obj = {} so I don't believe I care about the prototype chain here.

nem035
  • 34,790
  • 6
  • 87
  • 99
quickshiftin
  • 66,362
  • 10
  • 68
  • 89
  • 1
    `'config' in dataLayerObject` is redundant if directly followed by `typeof dataLayerObject.config == 'object'` (would otherwise be `undefined`) – Amit Nov 12 '15 at 16:12
  • 1
    also, one thing to note, `typeof null === 'object'` in ES5 and bellow – nem035 Nov 12 '15 at 16:16
  • @Amit not true, assume `var dataLayerObject = { config: null };` Then `'config' in dataLayerObject` evaluates to `true`, there for a subsequent check for `dataLayerObject.config.something` will throw an exception... – quickshiftin Nov 12 '15 at 16:17
  • @nem That is a very good point! – quickshiftin Nov 12 '15 at 16:18
  • Are you trying to find properties only on the object, or on the prototype chain as well? Seems from your code (by using `in`) that you are looking for prototype methods as well? – nem035 Nov 12 '15 at 16:20
  • I will update the question, these objects are presumed to be 'plain' objects, such as `var obj = {}` so I don't believe I care about the prototype chain here. – quickshiftin Nov 12 '15 at 16:23
  • @quickshiftin - either you didn't understand me, or your wrong. My comment is what nem claimd on his answer, and that's correct. – Amit Nov 12 '15 at 17:49
  • @Amit You are correct, I see what you mean now and did not at first read. My apologies. I didn't realize calling `typeof` on a know object for an unknown property would simply return `'undefined'` instead of throwing an Exception in the case where the property doesn't exist. – quickshiftin Nov 12 '15 at 17:58
  • @quickshiftin one clarification, since your question title and your code don't exactly match. Do you need to write a getter method or a method that checks existence? – nem035 Nov 12 '15 at 18:02
  • @nem Sorry for this, I want the method to return the value (I have written one, do you think I should post my own answer, or amend the question with it?). Why did you delete your answer? I found it very helpful and was about to give you an upvote when I found it deleted! – quickshiftin Nov 12 '15 at 18:05
  • @quickshiftin I deleted my answer temporarily to edit and fix some stuff, it should be there now. You can post your own answer, if that's the best one for you and you should accept it as well but it would be good to edit the question title to better explain what you needed. – nem035 Nov 12 '15 at 18:06

4 Answers4

6

The proper way to check for object properties in javascript is the Object.hasOwnProperty() method.

example:

var Person = {
  first_name: 'Fred',
  last_name: 'Flintstone'
};

if ( 'object' === typeof Person && Person.hasOwnProperty('first_name' ) {
  window.alert('the property exists!');
}

EDIT

for checking for nested object properties, you can try something like this:

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

  for (var i = 0; i < args.length; i++) {
    if (!obj || !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
tdc
  • 5,174
  • 12
  • 53
  • 102
2

First of all, you don't need to check for both property in object && typeof obj[property] == 'object', you can only use the typeof to take care of both checks. The reason is that, if that obj.property doesn't exist, typeof will return undefined.

Therefore, you can modularize your approach by writing a small utility function that checks if something is an object:

function isObject(o) {
    return typeof o == 'object' && o !== null; // take care of the corner case that typeof null == 'object'
}

Then just use it on your necessary object chain to find a property by checking that all of its owning objects exists:

function getChecklistUrl() {
    if(isObject(dataLayerObject) && 
       isObject(dataLayerObject.config) &&  
       isObject(dataLayerObject.config.checklist)) { 

        return dataLayerObject.config.checklist;
    }
    return null;
}

var dataLayerObject = {
    config: {
         checklist: ['array of stuff']
    }
}

function isObject(o) {
  return typeof o == 'object' && o !== null;
}

function getChecklistUrl() {
  if (isObject(dataLayerObject) &&
    isObject(dataLayerObject.config) &&
    isObject(dataLayerObject.config.checklist)) {

    return dataLayerObject.config.checklist;
  }
  return null;
}

console.log(getChecklistUrl()[0]);

This makes the code more organized and easier to read, IMHO.

You can also do something like a getter for your objects that take a dot-separated string and return you the property or null if the property doesn't exist:

function getObjProperty(obj, propString) {
    if(!isObject(obj) || !propString || typeof propString != 'string') {
        return null;                                 // make sure obj is an object and propString is a non-empty string
    } 

    var props = propString.split('.');
    for (var i = 0, l = props.length; i < l; i++) {
        var p = props[i];
        if(!isObject(obj[p])) { return null; }       // if current property isn't an object, return null
        obj = obj[p];                                // otherwise update the object to the next one down the property chain
    }
    return obj;
}

You would use it like: getObjProperty(dataLayerObject, 'config.checklist');

var dataLayerObject = {
  config: {
    checklist: ['array of stuff']
  }
};

function isObject(o) {
  return typeof o == 'object' && o !== null;
}

function getObjProperty(obj, propString) {
  if (!isObject(obj) || !propString || typeof propString != 'string') {
    return null;
  }

  var props = propString.split('.');
  for (var i = 0, l = props.length; i < l; i++) {
    var p = props[i];
    if (!isObject(obj[p])) {  // 
      return null;
    } // if current property isn't an object, return null
    obj = obj[p]; // otherwise update the object to the next one down the property chain
  }
  return obj;
}

console.log(getObjProperty(dataLayerObject, 'config.checklist'));

Or you can achieve this with a fairly straightforward recursive method

NOTE:

The examples above check the prototype chain as well. If you don't want this, you should use hasOwnProperty when checking if a property is an object to also check that the property exists on the testing object.

Prefix's answer shows one variation of such an approach.

Community
  • 1
  • 1
nem035
  • 34,790
  • 6
  • 87
  • 99
  • This is very similar to what I had going in my code to begin with. The question was more around the checking component which is where the original title came from. I really appreciate your answer and effort. – quickshiftin Nov 13 '15 at 13:51
  • No problem mate, glad to help :) – nem035 Nov 13 '15 at 14:41
0

You could do something like this if you want it to be short, but it doesn't check if the dataLayer is an object, so if it's a function with a config and checklist, it'll also return it. But since it's a custom object, it might not be an issue. You can add the 'in' or 'hasOwnProperty' checks if needed, or use a ternary if you prefer it.

var getChecklistUrl = function getChecklistUrl() {
    return dataLayerObject && dataLayerObject.config && dataLayerObject.config.checklist || null;
}
Shilly
  • 8,511
  • 1
  • 18
  • 24
0

You can always shorten it up by taking advantage of casting:

if ( typeof dataLayerObject == 'object' &&
     dataLayerObject.config &&
     dataLayerObject.config.checklist &&
     typeof dataLayerObject.config.checklist == 'object') {
  // do something
}

If you don't really need to check that checklist is actually an object, you can get rid of the last typeof. The first typeof is required since your variable looks like it might be global.

Ivan
  • 10,052
  • 12
  • 47
  • 78