Search code examples
javascriptflow-control

Proper getting of an object property in Javascript


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.


Solution

  • 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.