Utilize the as
clause within a mapped type to selectively filter out keys of a type:
type KeyOfType<T, V> = keyof {
[P in keyof T as T[P] extends V ? P : never]: any
}
The as
clause in a mapped type enables us to modify the key's name. If a key is mapped to never
, it will be excluded from the resulting type. Further information can be found in this PR
Before TS 4.1
This version remains functional post 4.1, although the alternative approach is easier to comprehend.
You can achieve this by utilizing conditional and mapped types
type KeyOfType<T, U> = {[P in keyof T]: T[P] extends U ? P: never}[keyof T]
Let's delve deeper into the concept.
We start with a mapped type that mirrors the properties of the original type T
, essentially a basic standard mapped type:
type KeyOfType<T> = { [P in keyof T]: T[P] } // New Type identical to the original
T[P]
refers to a type query denoting the type of the key P
in type T
. We can simplify this to just P
, indicating that the type of the new property matches its name:
type KeyOfType<T> = { [P in keyof T]: P }
// Therefore,
KeyOfType<{ a: number, b: string }> == { a: 'a', b: 'b' }
We introduce a type query to retrieve all the keys of the type again. Typically, T[keyof T]
retrieves all property types of a type. Applying this to our mapped type (where property types match key names), we essentially revert back to keyof T
:
type KeyOfType<T> = { [P in keyof T]: P }[keyof T]
// Therefore,
KeyOfType<{ a: number, b: string }> == 'a'|'b'
Now we incorporate a conditional type to selectively choose not always select P
. By setting the type of the property in the mapped type to never if it does not meet a specified constraint (T[P]
), we effectively employ the rule A | never == A
.
To specify the constraint, we add an additional generic parameter U
and apply a conditional type defined as
T extends U ? TypeIfTrue: TYpeIfFalse
. Combining these elements results in:
type KeyOfType<T, U> = {[P in keyof T]: T[P] extends U ? P: never}[keyof T]