3

Let's say I have a JavaScript object like:

var obj = {};
obj.computers = {};
obj.computers.favorite = "Commodore 64";
obj.computers.popular = "Apple";

Now, I can easily check for null like:

if(obj != 'undefined' && obj != null) {
    if(obj.computers != 'undefined' && obj.computers != null)) {
        .....

As you can see, if I need to see if obj.computers.favorite has been set, I have to really nest some conditional logic there. Some of our objects go 3, 4, 5 levels deep.

This is what I would like to be able to do:

var fav = obj.computers.favorite || 'Unknown';

But I realize I would need to wrap that in some method. Something like:

var fav = getValueOrDefault(obj.computers.favorite, 'Unknown');

Suggestions appreciated.

Thanks

EDIT

My checking for 'undefined' isn't actually what I use. It just came out of my head when asking the question. lol

But I was wondering, could I just wrap in a try/catch and throw default if exception?

function(obj, default) {
    try {
        if(obj != null) {
            return obj;
        } else {
            return default;
        }
    } catch(ex) {
        return default;
    }
}

Also, thanks to Shusi for pointing the redundant vars.

cbmeeks
  • 10,718
  • 18
  • 79
  • 134
  • `obj != 'undefined' && obj != null` is incorrect, since to get a string value `'undefined'`, you'll need typeof. This is a common bug. But either way, it is needlessly verbose. Just do `obj != null`, and it will test for `undefined` at the same time. – I Hate Lazy Oct 01 '12 at 20:29
  • `getValueOrDefault(obj.computers.favorite, 'Unknown')` would throw an exception if `obj.computers` were undefined. You'd have to wrap it in a string and do incremental object parsing. – saml Oct 01 '12 at 20:29
  • @saml exactly. Which is what I'm trying to avoid. Our JS objects go really deep sometimes. I guess I could just do a try/catch and return default if exception? – cbmeeks Oct 01 '12 at 20:38
  • Optional Chaining is what you want https://stackoverflow.com/a/60845999/2100372 – zoran404 Mar 25 '20 at 09:34

7 Answers7

6

You can really write:

(obj && obj.computers && obj.computers.favorite) || 'Unknown'
CD..
  • 65,131
  • 24
  • 138
  • 151
  • +1, but see my answer to a general solution which doesn't clutter the code with many `and`s and is more useful if OP uses this pattern more than once. – Raffaele Oct 01 '12 at 20:56
3

You can do it with a helper function. If I understand your problem correctly, your objects may have arbitrary deep objects, and you want to access these arbitrary nested properties without cluttering your code. I built a Nested function which lets you get() arbitrary properties if they are set, or a default if they are not.

var Nested = function() {};
// prop is a dot-separated path like "foo.bar.baz"
Nested.prototype.get = function(prop, _default) {
    var current = this;
    $.each(prop.split("."), function(i, e) {
        current = current[e] || undefined;
        if (current == undefined)
            return false;
    });
    return current || _default;
}

You can then write code like this

var fav = obj.get("computers.favourite", "Linux");
// this emulates
var fav = obj.computers.favourite || "Linux"; // throws error

As you can see, it's not so much more typing. Sure, it doesn't feel like regular Javascript... Here is the fiddle.

Raffaele
  • 19,761
  • 5
  • 42
  • 81
  • 1
    err, you need to change the var declaration to `var current = this;` and in the code `current = current[e] || undefined;`. the fiddle then will work correctly. –  Oct 01 '12 at 20:55
  • you should mention your answer requires jquery. – DrCord Oct 09 '15 at 17:10
3

I wrote this to help you deal with one of your questions: "I need to see if obj.computers.favorite has been set".

Object.prototype.isset = function (/* string */ full_path)
{
    var props = full_path.split('.');
    var self = this; /* note that self is usually the window object */

    for (var ii = 0; ii < props.length; ++ii)
    {
        var prop = props[ii];
        var hasMoreComing = ii < props.length - 1 ? true : false;

        if (self[prop] !== null && typeof self[prop] === 'object' && hasMoreComing)
        {
            self = self[prop];
            continue;   // Move up one level.
        }
        else if (hasMoreComing)
            return false;    // ..because user queries a subproperty of a value type

        return self.hasOwnProperty(prop);
    }
};

Test-code:

var test = {};

test.kuk = {};
console.log( test.isset('kuk') );  // Prints true.

test.kuk.fitta = {};
console.log( test.isset('kuk.fitta') ); // Prints true.

test.kuk.fitta = null;
console.log( test.isset('kuk.fitta') ); // Prints true.

test.kuk.fitta = undefined;
console.log( test.isset('kuk.fitta') );  // Prints true

delete test.kuk.fitta;
console.log( test.isset('kuk.fitta') );  // Prints false

test.kuk.fitta = 123;
console.log( test.isset('kuk.fitta.doesnt.exist') );  // Prints false
Martin Andersson
  • 14,356
  • 8
  • 77
  • 106
1

following function will take string as parameter and return object if exist

 function getValueOrDefault(str , obj, deflt){
    var a = str.split("."); o = obj;
    for(var i =0; i < a.length; i++){
         o = obj[a[i]];
         if(!o){
           return deflt;
         }
    }
      return o;
}

var obj = {};
obj.computers = {};
obj.computers.favorite = "Commodore 64";
obj.computers.popular = "Apple";
getValueOrDefault('computers.favorite', obj, 'Unknown');

Note: You must not use var while assigning properties to object eg. var obj.computers.favorite is SYNTAX error.

Anoop
  • 22,031
  • 9
  • 59
  • 70
  • I think you could swap `obj` and `deflt` params and change the `o = obj || window`. so you could write `getValueOrDefault('obj.computers.favorite', 'unknown')` –  Oct 01 '12 at 20:45
  • Yes, you are right. but this solution was more specific to above question. – Anoop Oct 01 '12 at 20:47
1

Unfortunately, there's not a super easy way to get around this, but you don't need to check for both null and undefined. Because null and undefined are both falsey, you can just do:

if (obj && obj.computers) {
  var fav = obj.computers.favorite || 'unknown';
}

It doesn't actually get around what your complaint is, but it's less painful than what you'd think.

saml
  • 6,262
  • 1
  • 30
  • 29
1

The optional chaining operator is a more recent way to handle this:

let favorite = obj?.computers?.favorite;

Combining this with the nullish coalescing operator, you can do precisely what was originally requested:

let favorite = obj?.computers?.favorite ?? 'Unknown';

Note that neither of these operators is universally supported yet though.

John Galambos
  • 2,763
  • 2
  • 22
  • 23
0

You can also do this with JavaScript ES5 reduce function :

function get(root, member) {
    return member.split('.').reduce((acc, value) => {
        return (acc && typeof acc[value] !== 'undefined') ? acc[value] : undefined
    }, root);
}

It makes a array out of the member string, then the accumulator progressively traverse within the object as the members are reduced.

You can use it like this, even with arrays :

let obj = {
    computers : {
        favorite : "Commodore 64",
        popular : "Apple",
        list: [
            {
                sn : "TDGE52343FD76",
                price: "9867.99",
                options: {
                    fanless: true
                }
            }
        ]
    }
}

get(obj, 'computers.popular');
// => "Apple"
get(obj, 'computers.list.0.sn');
// => "TDGE52343FD76"
get(obj, 'computers.list.0.options.fanless');
// => true
get(obj, 'computers.list.0.options.runsWithoutEletricity');
// => undefined
get(obj, 'computers.list.0.options.runsWithoutEletricity') || "too bad..." ;
// => "too bad..."

There is a codepen to safely traverse js object

Gabriel Glenn
  • 897
  • 1
  • 10
  • 23