2

generaly, if I want to check if obj.foo.bar exits, I will do this:

if(
    typeof obj != "undefined" &&
    typeof obj.foo !== "undefined" &&
    typeof obj.foo.bar !== "undefined"
 ){ action() } 

this is quite ugly and repetitious, and sadly I have to use it a lot.

I believe there won't be any function isDefined() available to do so, because even empty function (function isDefined(){}) will throw an error when if(isDefined(obj.foo.bar)){action()}

So, is there any smart way to check that obj.foo.bar exits?

EDIT:

  1. Approaches using string parsing (or just passing variables as string) are extremely problematic - since minifier might be in use.
  2. typeof obj != "undefined" could be replaced with obj. This solution still doesn't satisfy me.

EDIT 2 - Solution:

as proposed here

var a = {
    b: {
        c: 'd'
    }
};

function isDefined(fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isDefined(function () { return a.b.c; }), //return true
    isDefined(function () { return a.b.c.d.e.f; }) //returns false
);
// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

I have approved @T.J. Crowder's solution and not @somethinghere's one because at the end the regular solution is shorter, and because try catch statements have other purpose and can cause problems

Community
  • 1
  • 1
yonatanmn
  • 1,590
  • 1
  • 15
  • 21

3 Answers3

4

You can make that a lot less ugly, because you don't need typeof for most of it:

if (obj && obj.foo && typeof obj.foo.bar !== "undefined") {
    action();
}

If obj.foo.bar cannot be a falsey value (0, "", NaN, null, undefined, or false), you don't need typeof for the last item, either:

if (obj && obj.foo && obj.foo.bar) {
    action();
}

If you really want an isDefined function, you have to get into string parsing as outlined in this other question and its answers, but that can be a problem if you use a minifier that renames (shortens) property names, and does involve string parsing.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    This is how I usually do it, too. Clean, short, and reasonably foolproof. +1 – Cerbrus Mar 24 '15 at 10:15
  • Actually that's what I do, but it is still not elegant. I started working on an answer based on string parsing (note: the referenced SOF thread is answering a different question, and still needs adjustments), thankfully you warned me about the minifier !!. I will work on the other aproach (`isDefined(foo,bar, obj, more, more)`) - if you have a solution faster please post – yonatanmn Mar 24 '15 at 10:26
  • 1
    @yonatanmn: *"I will work on the other aproach (isDefined(foo,bar, obj, more, more))"* You **can't** do it that way without using strings. – T.J. Crowder Mar 24 '15 at 10:30
1

You can actually make this a universal function as such:

function isDefined(base){
    // Run through any number of passed arguments, ignoring the first one (`base`)
    for(var i = 1; i < arguments.length; i++){
        // While running, see if they exist and assign it to base, then continue
        base = typeof base[arguments[i]] != "undefined" 
            ? base[arguments[i]] : null;
        // if base is set to false break the loop as theres nothing deeper to find
        if(!base) break;
    }
    // return the value that base was set to
    return base;
}

And then call it like this:

var bar = {foo: {}};
console.log(isDefined(bar, 'foo')); ///= Will be bar[foo] (Object Object)
console.log(isDefined(bar, 'foo', 't')); // Will be 'null'

You will always have to pass one argument to make it work, but if you want a global existence you could pass the window object.

function isDefined(base){
    for(var i = 1; i < arguments.length; i++){
     base = typeof base[arguments[i]] != "undefined" 
      ? base[arguments[i]] : null;
     if(!base) break;
    }
    return base;
}


var bar = {foo: {}};
document.write(isDefined(bar, 'foo') + " / " + isDefined(bar, 'foo', 't'));

If you want to see if bar exists first, try using isDefined(window, 'bar', 'foo');

Update

I noticed that values set to false will return false as well, so null is our saviour here.

Update 2

Since you bring up the minifier issue, there is one way that is semi-elegant and allows for single line deployment, but you can't wrap it in a function:

var bar = {};
try { var defined = typeof bar.foo !== "undefined"; } catch(e) { var defined = false; } // defined will be false
var bar = {foo:{}};
try { var defined = typeof bar.foo !== "undefined"; } catch(e) { var defined = false; } // defined will be true

The annoying thing is it needs to be executed before your if, but it does simplify your if quite a bit:

var bar = {};
try { var defined = typeof bar.foo !== "undefined"; } catch(e) { var defined = false; }
if(defined){
    // Code here
}

Hope that helps.

Update 3

There is a way to wrap it in a function after all, hidden quite deep but here is a direct link to the underrated answer: javascript test for existence of nested object key

The key is to wrap the value in a function that returns the value at the time. Its quite clever!

Community
  • 1
  • 1
somethinghere
  • 16,311
  • 2
  • 28
  • 42
  • 1
    @yonatanmn Updated with a possible `try/catch` solution, but as noted here (and honestly, I only saw this _after_ I posted), theres not really any other way: http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key – somethinghere Mar 24 '15 at 10:45
  • Great! I was just about to propose similar solution, but your implementation is better. The problem here is that after all the @T.J.Crowder solution (edit 2) is still more elegant. but I give up :). thanks all – yonatanmn Mar 24 '15 at 10:53
  • Looked at your link, there is a way to wrap it in a function. I'll post it in my question – yonatanmn Mar 24 '15 at 10:59
  • @yonatanmn Ooh yeah thats clever, I hadnt scrolled down that much. I'll certainly append a link to that directly! – somethinghere Mar 24 '15 at 11:04
0

Something like this should do the trick:

function isDefined(obj, keysString) {
    //get all the keys
    var keys = keysString.split('.');
    var tmp = obj;

    for(var i = 0; i < keys.length; i++) {
        tmp = tmp[keys[i]];

        //slowly construct your 'deep' object while checking if you can       
        if(typeof tmp === 'undefined') {
            return false;
        }

    }

    return true;
}

Usage:

isDefined({test: {nested: true}}, 'test.nested'); //return true
isDefined({test: {nested: true}}, 'test.nested.supernested'); //return false
isDefined({test: {nested: true}}, 'test.othernested.supernested'); //return false

JsFiddle Here

Be careful with minifier that may change the name of your keys.

Pierrickouw
  • 4,644
  • 1
  • 30
  • 29