We can make custom type level function in order to achieve or omitting by value type or picking by value type. Below both options:
// picks given values
type PickByValue<T, V, _Keys extends keyof T = {
[K in keyof T]: T[K] extends V ? K : never
}[keyof T]> = Pick<T, _Keys>
type Numbers = PickByValue<All, number>
type Strings = PickByValue<All, string>
// omits given values
type OmitByValue<T, V, _Keys extends keyof T = {
[K in keyof T]: T[K] extends V ? K : never
}[keyof T]> = Omit<T, _Keys>
type Bools = OmitByValue<All, number | string>
The key to the success is how union type treads never
, its neutral element for |
operator, it means it is just skipped and has no impact at the type. Consider small proove:
type X = 'a' | never | 'b' // evaluates to just 'a' | 'b'
Thanks to that we can use never
in order to skip some keys. That is exactly visible in this part:
{
[K in keyof T]: T[K] extends V ? K : never
}[keyof T]
What is happening exactly is - we create mapped type with values equal keys, but only if the value type is ok, if not we use never
, and after that we take these values by index [keyof T]
. Lets recap step by step what is happening:
- mapped type makes mapped type
key-> (key | never)
[keyof T]
gathers all value type as union (and never
elements are not considered)
- we now have all keys which were not skipped by
never
- we use these keys in utility type
Pick
or Omit