3

We have an object (referenced by data) and we want to retrieve the value of a nested property. Ideally, we would like to do it like so:

value = data.category3.section2.article4.title;

We cannot do this like so, because the above line throws a reference error if any of the mediate objects (category3, section2, or article4) are not defined (at the corresponding positions) inside the data object.


Now, to nullify any potential reference errors that might be thrown, we could just place the above line inside a try-catch statement:

try {
    value = data.category3.section2.article4.title;
} catch (err ) {}

This works! However, I am not confident that relying on try-catch in such a way is a good practice. The alternative solution would be to manually traverse to the desired property value. I have written a compact utility function that accomplishes that:

function get( val, names ) {
    names = names.split( '.' );    
    while ( val && names.length ) { val = val[ names.shift() ]; }    
    return val;
}

Now we can get the property value like so

value = get( data, 'category3.section2.article4.title' );

So, my question is:

Is the try-catch approach a valid solution? Or are there valid reasons why it should be avoided?

Btw, the try-catch approach is heavily biased in this thread: What's the simplest approach to check existence of deeply-nested object property in JavaScript?

Community
  • 1
  • 1
Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • it might be easer to just use try catch – david Nov 21 '11 at 18:00
  • possible duplicate of [What's the simplest approach to check existence of deeply-nested object property in JavaScript?](http://stackoverflow.com/questions/6927242/whats-the-simplest-approach-to-check-existence-of-deeply-nested-object-property) – Patricia Nov 21 '11 at 18:03
  • @Patricia: the OP explicitely mentioned this question in this post. I think he wants to compare them more. – hugomg Nov 21 '11 at 18:42
  • @missingno yes, i saw that, and that question exactly answers his question. clearly it is acceptable, it's the marked answer which has been upvoted several times. – Patricia Nov 21 '11 at 18:56
  • @Patricia My question is not a duplicate of that other question. I am not asking what the simplest way to achieve that task is. I'm asking whether or not `try-catch` is a valid solution. That issue is not addressed in that other question, and it would not be convenient to address it now (in that other question). – Šime Vidas Nov 21 '11 at 19:59
  • Check out the performance comparison of the methods discussed here: http://jsperf.com/accessing-property-chains. Try/catch is bad if an exception gets thrown. – Chris Baker Nov 21 '11 at 21:14

5 Answers5

3

Why not:

var value = data && 
    data.category3 && 
    data.category3.section2 && 
    data.category3.section2.article4 && 
    data.category3.section2.article4.title;

That is safe (if any of the objects in the traversal chain are not set, value will be null). That is a little neater than a bunch of if blocks, and avoids (?mis)using exceptions.

Another use of that method to provide a default value on failure:

var value = data && 
    data.category3 && 
    data.category3.section2 && 
    data.category3.section2.article4 && 
    data.category3.section2.article4.title || 'default value';
Chris Baker
  • 49,926
  • 12
  • 96
  • 115
  • That's kind of silly. If you have such a long chain in real code and needed to constantly access these deeply nested properties, you would do something other than continuing to traverse the chain every third line. – Chris Baker Nov 21 '11 at 20:59
  • On the other hand, on Firefox 3.6 the try-catch version is both the fastest and the slowest, depending on wheter it throws or not oO – hugomg Nov 21 '11 at 21:08
  • Erp, I was reading wrong. http://jsperf.com/accessing-property-chains - looks like this method is faster (discounting try/catch) – Chris Baker Nov 21 '11 at 21:15
1

Both are fine. The only major differences between them I can think of are that

  1. The try-catch may cause a debugger to unecessarily halt too often if you tell it to stop on all exceptions.

    This is relevant you need to debug code that is swallowing exceptions. For example, some promise libraries wrap all callbacks in a try-catch block.

  2. The string splitting version can't easily cope with properties that contain a dot in them

    var x = {'.': {a: 17}};
    try{ obj['.'].a }catch(e){}
    get(/*???*/)
    

If you want something robust that avoids both pitfalls I would suggest a function that can (at least optionally) directly receive a list of properties.

get(val, ['prop1', 0, '.', 'category2']);
Community
  • 1
  • 1
hugomg
  • 68,213
  • 24
  • 160
  • 246
1

I think the differences here are going to be mostly contextual - it depends on the data you're trying to access and what you want to do with it.

For example, the second function will return equivalent undefined values for a variety of circumstances, including both data.category3 === undefined and data.category3.section2.article4.title === undefined. Using try/catch here tells you that you have an actual traversal error, rather than a property that hasn't been set, which you might want to handle differently.

nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • Good point. But the traversal problem can be solved by doing an explicit `prop in val` test instead of using truthyness tests in the loop. – hugomg Nov 21 '11 at 19:04
  • @nrabinowitz Yes, the function I wrote doesn't provide that kind of information. But, of course, it can be enhanced to provide that information. Note that the `try-catch` solution provided in my question also doesn't provide that information since the error is merely caught but not processed. So technically both solutions provide equivalent functionality (which was my intention). – Šime Vidas Nov 21 '11 at 20:09
0

I have seen the answers here and I think that the traversing is your best move, but it looks quite bothersome. You can make a function that traverses it for you or you can use the almighty brototype library found at: https://github.com/letsgetrandy/brototype

This way you can do something like this:

if (Bro(data).doYouEven('category3.section2.article4.title')) {
    value = data.category3.section2.article4.title;
}

or you can use a callback:

Bro(app).iDontAlways('category3.section2.article4.title')
    .butWhenIdo(function(title){
        value = title;
    });

I think everyone should check this amazing library out, and code with great bro-ness.

If you dislike the brototype, you can indeed use your own get function.

Jimmy Knoot
  • 2,378
  • 20
  • 29
0

Abusing try catch like this is a dirty hack.

Try catch is there to catch exceptions you throw. Exceptions are used for exceptional cases.

In this case both cases are wrong. You should never have to traverse data.category3.section2.article4.title; where every step can fail.

You should simply be able to assert that if data has a category then it should have a section, article and title.

I say refactor the code so you don't have multiple levels that can fail.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • If the object is coming from a third party, though, there is value in having a best-practice approach to safely accessing deep properties. In my completely objective view, my approach is the best :P – Chris Baker Nov 21 '11 at 20:38