Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 946
quickshiftin Avatar asked Feb 09 '23 14:02

quickshiftin


2 Answers

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
like image 84
tdc Avatar answered Feb 15 '23 22:02

tdc


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.

like image 30
nem035 Avatar answered Feb 15 '23 22:02

nem035