0

Consider the following company object:

var employee = {
    empID: 45435,
    salary: 44000,
    title: "Janitor",
    firstName: "Richard",
    lastName: "Stallman"
}

var department = {
    name: "Maintenance",
    building: "H",
    room: 404,
    departmentLead: employee
}

var company = {
    name: "Bluth's Original Frozen Banana Stand",
    revenue: 'always',
    maintenanceDept: department
}

If I wanted to access the title property of the employee object, I could reference it with:

var title = company.maintenanceDept.departmentLead.title;

However, if departmentLead is undefined, a TypeError will be thrown. So, I must write a test before accessing title:

if(company && 
   company.maintenanceDept && 
   company.maintenanceDept.departmentLead &&
   company.maintenanceDept.departmentLead.title){ ... }

Is this the best way of doing this? Obviously, this example is contrived because I defined company before using it, so I know title exists. The same cannot be said for a company object returned from an AJAX call. A solution I've been using has been:

// Edit: I've been informed adding properties to Object.prototype is not a smart thing to do...so don't do this
Object.prototype.propertyWithPath = function (path) {
    var keys = path.split('.');
    var parent = this;
    var child;

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

        if (parent[child] === undefined) {
            return undefined;
        }
        parent = parent[child];
    }
    return parent;
};

Besides the performance hit, are there any downsides to using this method vs the alternative?

Stephen Melvin
  • 3,696
  • 2
  • 27
  • 40
  • 1
    possible duplicate of [javascript test for existence of nested object key](http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key) – Kevin Ji Feb 25 '14 at 15:55
  • 2
    *"`Object.prototype.propertyWithPath = ...`"* **Never** add enumerable properties to `Object.prototype`. The sheer volume of code you'll break as a result is staggering. In general, leave `Object.prototype` alone. If you *must* add to it, only add *non-enumerable* properties via `Object.defineProperty` (and note that `Object.defineProperty` cannot be properly shimmed on pre-ES5 engines like IE8.) – T.J. Crowder Feb 25 '14 at 16:01
  • 1
    @T.J.Crowder You're right, that is kind of a bone-headed thing to do. – Stephen Melvin Feb 25 '14 at 16:09

4 Answers4

0

Your choices are either to do the testing inline or use a function similar to the one you've shown. (But see the comment on the question: Never add enumerable properties to Object.prototype. Use a non-prototype function instead.)

Note that the test doesn't have to be an if, you can do this:

var title = company && 
            company.maintenanceDept && 
            company.maintenanceDept.departmentLead &&
            company.maintenanceDept.departmentLead.title;

title will either be undefined (or another falsey value), or the title, because JavaScript's && operator is curiously powerful: If the value on the left is falsey, it returns that value; otherwise, it returns the value on the right. It doesn't always return true or false as it would in many other languages. So 5 && 7 is 7; 0 && 7 is 0.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

Besides the obvious performance hit that you mention, which personally would be enough to avoid it, here is another downside:

Object.prototype.propertyWithPath = function (path) {
    var keys = path.split('.');
    var parent = this;
    var child;

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

        if (parent[child] === undefined) {
            return undefined;
        }
        parent = parent[child];
    }
    return parent;
};

var foo = { bar: 1 };

for(var prop in foo)
    console.log(prop); //bar, propertyWithPath

If you want to extend Object.prototype, use Object.defineProperty and make in non-enumerable.

Johan
  • 35,120
  • 54
  • 178
  • 293
0

You could use a factory method to generate objects. That way the attributes will always be there:

var employee = function(params){
    return {
        empID: params.empID || false,
      salary: params.salary || false,
      title: params.title || '',
      firstName: params.firstName || '',
      lastName: params.lastName || ''
    };
}



var department = function(params){
    return {
        name: params.name || '',
        building: params.building || '',
        room: params.room || 0,
        departmentLead: params.employee || employee({})
    }
}

var emp1 = employee({
    empID: 45435,
    salary: 44000,
    title: "Janitor",
    firstName: "Richard",
    lastName: "Stallman"
})

var emp2 = employee({});

var dep1 = department({
    name: "Maintenance",
    building: "H",
    room: 404,
    departmentLead: emp1
})

var dep2 = department({});

console.log(dep2.departmentLead.firstName);
Rob M.
  • 35,491
  • 6
  • 51
  • 50
  • Thanks for the answer, but my question is more focused on objects that I didn't create myself. For example: A JSON object returned from an API call. – Stephen Melvin Feb 26 '14 at 16:17
-1

You can just wrap it in a try block:

try {
    company.maintenanceDept.departmentLead.title
} catch (e) {
    // You can run a regex on e.message to get the child property that caused the issue
}
Andreas Møller
  • 839
  • 5
  • 9
  • What effect will the try/catch have on compiler optimization? – Stephen Melvin Feb 25 '14 at 16:14
  • 1
    This is, of course, slower in the "not found" case (sometimes in the "found" case as well, I was a bit surprised to see). [Test when found](http://jsperf.com/test-vs-exception-present) | [Test when not found](http://jsperf.com/test-vs-exception-not-present). But 99% of the time, you don't care about speed anyway. *(Not my downvote, btw.)* – T.J. Crowder Feb 25 '14 at 16:18