0

Accessing properties of optional properties is always a hassle. Imagine the following object test1 (in TypeScript notation):

interface Test {
    a?: { b?: { c?: { d?: string } } };
}

const test1: Test = { a: { b: { c: { d: 'e' } } } };

The absence of each property has to be handled in order to receive the 'e' if it is there to avoid an error, eg.:

let result;
const test2: Test = { a: { b: {} } };

Trying to accessing the property d directly would throw an error, since c is undefined and obviously that has no property d.

result = test2.a.b.c.d // TypeError: undefined is not an object

So every property has to be checked manually:

let result;
if (test2 && test2.a && test2.a.b && test2.a.b.c && test2.a.b.c.d !== undefined) {
    result = test2.a.b.c.d;
}

What is the shortest and a best practice solution for this common problem?

A try/catch block could work, but does not seem to be short. Passing the test2.a.b.c.d as a function argument to a function handling the error also does not seem to work, since it would throw the error before entering the function.

cocoseis
  • 1,443
  • 1
  • 13
  • 35

2 Answers2

0

For years I have been doing it the long way. Just today I came up with this short solution making use of a callback function. In this way, an error of the execution can be caught inside the outer function. In addition, a second argument can be passed to set a value in case the property can not be found.

usage

result = getOptionalProperty(()=>test2.a.b.c.d);

implementation

getOptionalProperty = <T, U>(callback: () => T | undefined, fallback?: U): T | U => {
    let result: T | undefined;

    try {
        result = callback();
    } catch (e) {
        result = undefined;
    }

    return (result === undefined) ? fallback : result;
}
cocoseis
  • 1,443
  • 1
  • 13
  • 35
  • 1
    It seems like I have not been the first: https://silvantroxler.ch/2017/avoid-cannot-read-property-of-undefined/ – cocoseis Feb 23 '18 at 18:24
0

I use this function for this purpose

Object.resolve = function(path, obj, error) {
      var res = path.split('.').reduce(function(prev, curr) {
          return prev ? prev[curr] : undefined
      }, obj || self);
    var error = (typeof error == "undefined" ? undefined:error);
      if(!res) 
         return error;
      return res;
}

This accepts three parameters, first one is the path you want to access (say: a.b.c.d), second is the object itself and third is what it should return if it is not found. This function checks for the properties mentioned in the path till it found it otherwise it returns the third parameter.

See the examples below:

Object.resolve = function(path, obj, error) {
      var res = path.split('.').reduce(function(prev, curr) {
          return prev ? prev[curr] : undefined
      }, obj || self);
    var error = (typeof error == "undefined" ? undefined:error);
      if(!res) 
         return error;
      return res;
}


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

console.log(Object.resolve("a.b.c.d", test, "Not present"));
console.log(Object.resolve("a.b.d", test, "Not present"));

PS, You can this in your typescript code easily. I use it in my Angular 5 projects.

void
  • 36,090
  • 8
  • 62
  • 107
  • But your `path` is a `string`, right? Is using "inflection" considered a good practice in this case? – cocoseis Feb 23 '18 at 17:57
  • @cocoseis yes I don't think it causes any security concern. – void Feb 23 '18 at 17:58
  • 1
    What about after a basic refactoring procedure? Then it would break for sure. This would especially be a pity after writing type safe code using TypeScript. – cocoseis Feb 23 '18 at 17:59
  • You should not extend native JavaScript classes like that. – str Feb 23 '18 at 18:18