How to get a subset of `keyof T` whose value, T[K] are callable functions in Typescript
I want to filter keyof T
based on type of T[keyof T]
It should work like this:
type KeyOfType<T, U> = ...
KeyOfType<{a: 1, b: '', c: 0, d: () => 1}, number> === 'a' | 'c'
KeyOfType<{a: 1, b: '', c: 0: d: () => 1}, string> === 'b'
KeyOfType<{a: 1, b: '', c: 0: d: () => 1}, Function> === 'd'
Is this possible?
You can use the as
clause of a mapped type to filter the keys of a type:
type KeyOfType<T, V> = keyof {
[P in keyof T as T[P] extends V? P: never]: any
}
The as
clause of a mapped type allows us to manipulate the name of the key. If the key is mapped to never
the key gets removed from the resulting type. You can find more info in this PR
Pre TS 4.1
This version still works in post 4.1, just the other way is easier to read.
You can do this using conditional and mapped types
type KeyOfType<T, U> = {[P in keyof T]: T[P] extends U ? P: never}[keyof T]
Let's break things down a little bit.
We can start with a mapped type, that has the same properties of the same type as the original T
, this a simple standard mapped type:
type KeyOfType<T> = { [P in keyof T]: T[P] } // New Type same as the original
T[P]
is a type query and means the type of the key P
in type T
. We can change this to just P
, meaning that the type of the new property is the same as it's name:
type KeyOfType<T> = { [P in keyof T]: P }
// So
KeyOfType<{ a: number, b: string }> == { a: 'a', b: 'b' }
We can add a type query to this type to again get all the keys of the type. Generally a construct T[keyof T]
gets all the property types of a type. Applying this to our mapped type which has the property types the same as the key names we basically get back to keyof T
:
type KeyOfType<T> = { [P in keyof T]: P }[keyof T]
// So
KeyOfType<{ a: number, b: string }> == 'a'|'b'
Now we can add a conditional type to o not always select P
. Since A | never == A
we can set the type of the property in the mapped type to never if the type of the original property (T[P]
) does not meet a certain constraint.
To express the constraint we add an extra generic parameter U
and we use a conditional type which has the form T extends U ? TypeIfTrue: TYpeIfFalse
. Putting it together we get:
type KeyOfType<T, U> = {[P in keyof T]: T[P] extends U ? P: never}[keyof T]
awesome explanation!!
I would also add that you can set a default type to U
to be any key of T
like so:
type Keys<T> = {[P in keyof T]: T[P]}[typeof P]
type KeyOfType<T, U = Keys<T>> = {[P in keyof T]: T[P] extends U ? P : never}[keyof T]
I add U = Keys<T>
to the generic definition of KeyOfType
Thanks for the explanation!