Reputation: 1600
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:
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
Upvotes: 2
Views: 559
Reputation: 17340
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');
I noticed that values set to false
will return false as well, so null is our saviour here.
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.
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!
Upvotes: 1
Reputation: 4704
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
Be careful with minifier that may change the name of your keys.
Upvotes: 0
Reputation: 1074276
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.
Upvotes: 4