9

I'm trying to find an elegant way to check the if certain deep properties exist in an object. So practically trying to avoid monstrous protective checks for undefined eg.

if ((typeof error !== 'undefined') && 
  (typeof error.responseJSON !== 'undefined') &&
  (typeof error.responseJSON.error) && 
  (typeof error.responseJSON.error.message)) {
      errorMessage = error.responseJSON.error.message;
}

What I'm thinking about is a convenience-function like

if (exists(error.responseJSON.error.message)) { ... }

Any ideas? For convenience, the use of underscore-library is ok for the solution.

Marcus
  • 5,083
  • 3
  • 32
  • 39
  • You could do this by passing your request as a string to a function. The function should split the string by "." or something, then iterate over each segment looking for each value. There are other questions and good answers about this though: http://stackoverflow.com/questions/8817394/javascript-get-deep-value-from-object-by-passing-path-to-it-as-string – Olical Nov 15 '13 at 14:28
  • I'm thinking if there's a way of handling this for an object, not as a string? Of course I could just JSON.stringify the object, but I'd feel a lot better if it'd handle the objects directly. – Marcus Nov 15 '13 at 14:38
  • 1
    If you used an object you'd need to create the entire structure you're looking for (which is bulky and even worse than `&&` all over the place). A string may seem weird, but it's the most flexible and compact solution. An alternative is to pass an array, which is just the string pre-split. Then iterate over that drilling down into the target object. – Olical Nov 15 '13 at 16:28

2 Answers2

21

There are several possiblities:

Try-catch

try {
  errorMessage = error.responseJSON.error.message;
} catch(e) { /* ignore the error */}

Fails for:

Object.defineProperty(error, 'responseJSON', {
  get: function() { throw new Error('This will not be shown')
});

&&

errorMessage = error && error.responseJSON && error.responseJSON.error && error.responseJSON.error.message;

Fails for:

error.responseJSON = 0;
// errorMessage === 0 instead of undefined

function

function getDeepProperty(obj,propstr) {
  var prop = propstr.split('.');
  for (var i=0; i<prop.length; i++) {
    if (typeof obj === 'object')
      obj = obj[prop[i]];
  }
  return obj;
}

errorMessage = getDeepProperty(error, 'responseJSON.error.message');

// you could put it all in a string, if the object is defined in the window scope

Fails for:

// It's hard(er) to use

function alternative - see comment by @Olical

function getDeepProperty(obj) {
  for (var i=1; i<arguments.length; i++) {
    if (typeof obj === 'object')
      obj = obj[arguments[i]];
  }
  return obj;
}

errorMessage = getDeepProperty(error, 'responseJSON', 'error', 'message');
Tibos
  • 27,507
  • 4
  • 50
  • 64
6

Try this underscore mixin to look up a variable with a path. It takes an object and string and t

_.mixin({
    lookup: function (obj, key) {
        var type = typeof key;
        if (type == 'string' || type == "number") 
            key = ("" + key).replace(/\[(.*?)\]/, function (m, key) { //handle case where [1] may occur
                return '.' + key.replace(/["']/g, ""); //strip quotes
            }).split('.');
        for (var i = 0, l = key.length; i < l; i++) {
            if (_.has(obj, key[i])) 
                obj = obj[key[i]];
            else 
                return undefined;
            }
        return obj;
    }
});

Now call in your example:

_.lookup(error, 'responseJSON.error.message') // returns responseJSON.error.message if it exists otherwise `undefined`
megawac
  • 10,953
  • 5
  • 40
  • 61