How to write type-safe nested key paths in TypeScript
Recently I had to write a type-safe function that allowed to update (nested) paths of a JavaScript object. I wanted the function to prevent setting a wrong value to the requested path, even nested ones with optional values.
After playing a bit with TypeScript type system I came up with these types which I think can be useful and will be using for sure in future projects. You can play with it in this playground or see the code right below:
export type KeyPath<
T extends string,
K extends string,
Separator extends string = ".",
> = `${T}${"" extends T ? "" : Separator}${K}`;
export type KeyPaths<T extends object, P extends string = "", Separator extends string = "."> = {
[K in keyof T]-?: K extends string
? NonNullable<T[K]> extends object
? KeyPath<P, K, Separator> | KeyPaths<NonNullable<T[K]>, KeyPath<P, K, Separator>, Separator>
: KeyPath<P, K, Separator>
: never;
}[keyof T & string];
export type GetTypeAtKeyPath<T, Path extends string, Separator extends string = "."> = Path extends keyof T
? T[Path]
: Path extends `${infer K}${Separator}${infer Rest}`
? K extends keyof T
? NonNullable<T[K]> extends object
? GetTypeAtKeyPath<NonNullable<T[K]>, Rest, Separator>
: never
: never
: never;
Code language: TypeScript (typescript)
Let’s dig into how to use it and how KeyPath
, KeyPaths
and GetTypeAtKeyPath
work and how to use it.