Skip to content

Instantly share code, notes, and snippets.

@j1mmie
Forked from steven-schmoll-at/paths-of.ts
Created March 24, 2023 00:35
Show Gist options
  • Save j1mmie/03e1dfc7ca14296604843235ad32082a to your computer and use it in GitHub Desktop.
Save j1mmie/03e1dfc7ca14296604843235ad32082a to your computer and use it in GitHub Desktop.
A typescript type to extract the keys and sub-keys of objects as dot separated paths.
type CombineAll<T> = T extends {[name in keyof T]: infer Type} ? Type : never
type PropertyNameMap<T, IncludeIntermediate extends boolean> = {
[name in keyof T]: T[name] extends object ? (
SubPathsOf<name, T, IncludeIntermediate> | (IncludeIntermediate extends true ? name : never)
) : name
}
type SubPathsOf<key extends keyof T, T, IncludeIntermediate extends boolean> = (
`${string & key}.${string & PathsOf<T[key], IncludeIntermediate>}`
)
/**
* Extracts out the keys of the given type as dot separated paths.
* For example:
* {
* key1: string;
* key2: {
* sub1: string;
* sub2: string
* }
* }
*
* Will be extracted as:
* 'key1' | 'key2.sub1' | 'key2.sub2'
*
* If you provide a value of `true` for the IncludeIntermediate parameter, then it will
* also include the object keys by themselves:
* 'key1' | 'key2' | 'key2.sub1' | 'key2.sub2'
*
* This works to any depth, so you can have objects within objects, within objects, etc.
*
* NOTE: You cannot have circular types in this. ie. A child key cannot reference its own
* type or a parent type.
*/
export type PathsOf<T, IncludeIntermediate extends boolean = false> = CombineAll<PropertyNameMap<T,IncludeIntermediate>>
// Example types
interface LeafInterface {
end: string;
finish: string;
complete: string;
}
interface ChildInterface {
leaf: LeafInterface;
deadEnd: true;
}
interface ParentInterface {
child1: ChildInterface;
child2: ChildInterface;
child3: ChildInterface;
}
function onlyPathsOf(key: PathsOf<ParentInterface>) {
console.log(key);
}
function onlyPathsOfWithIntermediaries(key: PathsOf<ParentInterface, true>) {
console.log(key);
}
// Valid
onlyPathsOf('child3.deadEnd')
onlyPathsOf('child1.leaf.complete')
onlyPathsOf('child3.leaf.finish')
onlyPathsOfWithIntermediaries('child1.leaf')
// Not valid
onlyPathsOf('child4.no')
onlyPathsOf('child3.leaf')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment