DEV Community

Discussion on: Advanced TypeScript: reinventing lodash.get

Collapse
 
agrinko profile image
Alexey Grinko

Why use lodash get when the path is static (i.e. you know it in advance)? For the first example just use user?.address?.street, and your code is perfectly readable and type-safe.

If still wanna use get for some reason, add an explicit type cast:

(get(user, 'address.street') as UserInfo['address']['street'])
  .filter(...)
Enter fullscreen mode Exit fullscreen mode

This helps to understand the type from the first glance, especially when you look at the code not in your IDE but somewhere where types can't be inferred easily, e.g. on GitHub. And if type of 'street' property changes, you'll be alerted.

On the other hand, lodash's get is really useful when your path is determined dynamically in runtime, e.g. generated by some algorithm or provided by backend. And that's where inferring type is just impossible without an explicit runtime check.

So why complicate things to solve unrealistic problems?

P.S. Would be cool to see a real-life scenario for using this feature.

Collapse
 
tipsy_dev profile image
Aleksei Tsikov

Yes, I absolutely agree with you. For static paths, it's definitely better to prefer regular field access with an optional chaining. However, _.get is still widely used, and some might find it easier to replace a function call or augment lodash typings.

Speaking of type-casting, in the first place, you want to avoid it as much as possible. This thing makes type checking looser, and after all, that's not why we are enabling strict flag, right? In this particular case, you are giving TS a hint that this any is a more specific type. But get anyway returns any, and if for some reason type of address.street changes, TS will not complain. In the end, any could be cast to literally any type.

Regarding dynamic paths, of course, not every case is covered (and GetFieldType should probably return unknown when the field is not found). But this approach still works when you have a union of possible values

type Letter = 'alpha' | 'beta' | 'gamma'

const symbols = {
  alpha: { symbol: 'α' },
  beta: { symbol: 'β' },
}

function getSymbol(letter: Letter) {
  return symbols[letter]?.symbol // TS error: Property 'gamma' does not exist on type
}

function getSymbolWithPath(letter: Letter)/* : string | undefined */ {
  return getValue(symbols, `${letter}.symbol`)
}
Enter fullscreen mode Exit fullscreen mode

And anyway, you could think of this article as an exercise to better understand template literal and conditional types 🙂

As for a real-life scenario, we use it in a slightly different, a bit less dynamic way, to create strongly-typed APIs similar to react-table columns config. But that's a topic for another article 😉