DEV Community

Discussion on: I need to learn about TypeScript Template Literal Types

Collapse
 
nroboto profile image
Mike Nightingale

Thanks for the great article.

I was messing around with the string dot Path type, I think it can be made a little simpler by splitting it into two types

type Subpath<T, Key extends keyof T = keyof T> = (
  T[Key] extends Record<string, any> ?
    `${Key & string}.${Path<T[Key], Exclude<keyof T[Key], keyof any[]>>}`
  :
    never
);

type Path<T, Key extends keyof T = keyof T> = (
  Key extends string ?
    Key | Subpath<T, Key>
  :
    never
);
Enter fullscreen mode Exit fullscreen mode

Subpath gets the paths for properties of T, and Path stitches them all together into a single type.

PathType can also be made a lot more concise by using type intersection on Key and Rest

type PathValue<T, P extends Path<T>> = (
  P extends `${infer Key}.${infer Rest}` ?
    PathValue<T[Key & keyof T], Rest & Path<T[Key & keyof T]>>
  :
    T[P & keyof T]
);
Enter fullscreen mode Exit fullscreen mode

I'm not sure if this is easier to understand than the original though.

Here's the result.

Collapse
 
seanblonien profile image
Sean Blonien • Edited

This is great stuff!

I have been using your improved types for a better part of this last year, and I came into an example of a type that breaks the interface, and I honestly have no idea why. (Also doesn't work with original type)

Counter example of the interface not correctly idenying foo.test

@nroboto do you know what's going on here? Am i missing something?

Collapse
 
nroboto profile image
Mike Nightingale

The issue is that in the Subpath type we exclude all of the properties of an array, which includes the length property, this results in foo.length being excluded even though in this case length is a property on an object. One way to fix this is to change the exclusion to only apply if the type is an array:

type ExcludeArrayKeys<T> = Exclude<keyof T, T extends any[] | readonly any[] ? keyof any[] : never>

type Subpath<T, Key extends keyof T = keyof T> = (
  T[Key] extends Record<string, any> ?
    `${Key & string}.${Path<T[Key], ExcludeArrayKeys<T[Key]>>}`
  :
    never);
Enter fullscreen mode Exit fullscreen mode

Here's the TS playground.