Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to implement a TypeScript generic type for nested properties that resolves to their types?

How to create a generic type that resolves to the type of a nested property

Say I have a configuration type

type Config = {
  parent: {
    child: boolean
  }
}

I would like something similar to

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

type ChildConfigType = NestedType<Config, 'parent.child'> // boolean
type ParentConfigType = NestedType<Config, 'parent'> // {child: boolean}

I have a basic implementation for non nested types

function getProp<
    Type extends object,
    Key extends keyof Type
>(key: Key): Type[Key] {
    return {} as Type[Key]
}

getProp<Config>('parent')

I also have non nested helper types that work

type PropsOf<
    Type extends object
> = keyof Type

type TypeOfProp<
    Type extends object,
    Key extends PropsOf<Type>
> = Type[Key]

function getPropWithHelpers<
    Type extends object,
    Key extends PropsOf<Type>
>(key: Key): TypeOfProp<Type, Key> {
    return {} as TypeOfProp<Type, Key>
}

I also have a NestedPropsOf helper that resolves to a flattened union of the nested properties

type NestedPropsOf<Type extends object> =
  {
    [key in keyof Type & (string | number)]: Type[key] extends object
    ? `${key}` | `${key}.${NestedPropsOf<Type[key]>}`
    : `${key}`
  }[keyof Type & (string | number)]

type NestedConfigProps = NestedPropsOf<Config> // 'parent' | 'parent.child'

And I am trying to implement a function with the signature

function getNestedProp<
    Type extends object,
    Key extends NestedPropsOf<Type>
>(key: Key): NestedTypeOfProp<Type, Key> {
    return {} as NestedTypeOfProp<Type, Key>
}


getNestedProp<Config>('parent.child')

I am starting to believe this is not possible because the types can’t directly manipulate strings so I cannot remove the first segment of the path.

Ts Playground

>Solution :

It is actually possible to manipulate strings using template literals and infer keyword.

Utilities:

Dot is used to concatenate two strings with .:

type Dot<T extends string, U extends string> = "" extends U ? T : `${T}.${U}`;

StopFields is the type on which we should stop going further in the types; in our case, those are primitives:

type StopFields = string | number | boolean | symbol;

PathToFields returns all possible paths to properties of the passed type using mapped types. It ignores arrays, which can be adjusted.

type PathsToFields<T> = T extends StopFields
  ? ""
  : {
      [K in Extract<keyof T, string>]: NonNullable<T[K]> extends Array<any>
        ? never
        : K | Dot<K, PathsToFields<T[K]>>;
    }[Extract<keyof T, string>];

Implementation:

type NestedType<T, Path extends PathsToFields<T>> = Path extends keyof T
  ? T[Path]
  : Path extends `${infer Property extends keyof T & string}.${infer SubField}`
  ? SubField extends PathsToFields<T[Property]>
    ? NestedType<T[Property], SubField>
    : never
  : never;

NestedType expects a type and its paths to fields. It first checks if the path is the property of the object, in that case, returns the type under that property, otherwise splits the path with . where the left part should be the key of the passed object and the part after the . is the remaining path. If all conditions are true, we invoice NestedType for the property that we got from splitting the path and passing the remaining path.

Usage:

type ChildConfigType = NestedType<Config, "parent.child">; // boolean
type ParentConfigType = NestedType<Config, "parent">; // {child: boolean}

playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading