Resolving deeply nested nullable values in Typescript using idx

Suppose you have a Typescript project with strictNullChecks on, and a deeply nested type that you might get.

interface IUser {
  user: {
    id: string;
    friends?: Array<{
      id: string;
      languages?: Array<{
        code: string;
        primary?: boolean;
      }>
    }>
  }
}

On account of user.friends or, user.friends[n].languges or, user.friends[n].languages[n].primary being null/undefined, trying to traverse through this structure without proper null checks would result in Object is possibly 'undefined' compilation errors.

Doing deep null checks like this works, but is cumbersome, and doesn’t scale very well for readability:

const firstFriendsPrimaryLanguage = 
  user.friends && 
  user.friends[0] && 
  user.friends[0].languages && 
  user.friends.languages.filter(...)

Why not use non-null assertion?

The alternative is to use the non-null assertion, which lets us do user.friends![0]!.languages!..., but if any path is null, this would throw.

What about lodash.get?

Lodash’s get means well, but you use the type-safety. For an example, if you have this,

const firstFriendsLanguages = _.get(user, `friends[0].languages`)`

Typescript cannot infer the type of firstFriendsLanguages automatically, and you completely exclude this expression from future type refactoring.

Enter idx

Using idx, which lets us access “arbitrarily nested, possibly nullable properties on a JavaScript object”, and the non-null assertion operator shown above, we can come up with a concise solution to our problem that is both concise and type-safe.

Example:

const firstFriendsLanguages = idx(user, u => u!.friends![0]!languages);

Idx works by evaluating the expression given in terms of a function in the second argument. If it throws as a result of evaluating that function, idx will rescue that and just return undefined.

So, just to summarise, we make our accessor concise by using non-null assertions, and deflect the potential exception that might break our code by handling the excpetion (via idx).

It’s such a simple solution, that you should be able to implement the same utility in your codebase by following this file.

Written on November 28, 2017