This type challenge is to create your own version of pick (without pick obviously). I decided not to use any utility types
Link to Challenge: https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md
Table Of Contents
Tags
- Indexed Access Types/Lookup types
- Mapped types
- Generic constraints
Explanation
We need to create a new object type with only the properties in both Type
and Union
type MyPick<Type, Union> = /* ... */
Lookup types
To get the type of the keys (keys are also known as indexed access types or lookup types) in Chair
, we can use square bracket notation just like accessing a property or method in a JavaScript object
type Chair = {
color: string,
legCount: number,
isComfortable: boolean
}
Chair["color"] // string
Chair["legCount"] // number
Note that only types can be used for indexing
const key = "color"
Chair[key] // Type "key" cannot be used as an index type
Mapped Types
How can we iterate through the union and create a new object type? The answer is mapped types
For example, we can set each key of Chair
to a boolean like this with dynamic keys and the in
operator
type ChairKeysUnion = "color" | "legCount" | "isComfortable"
type BooleanChair = {
[Key in ChairKeysUnion]: boolean
}
/*
type BooleanChair = {
color: boolean;
legCount: boolean;
isComfortable: boolean;
}
*/
Note that Key
in this example is a parameter, so it is just a placeholder name. It could be called Property
or Jimothy
and work just as well
Generic Constraints
But why can't we just combine mapped types and lookup types to set each property in the union to the appropriate lookup type like this?
type MyPick<Type, Union> = {
[Key in Union]: Type[Key]
}
At this point, MyPick
is not typesafe—it accepts any and all properties in Union
. In the case a property doesn't exist in the Type
passed to MyPick
, it will be on the new object type with a value of unknown
type NewChair = MyPick<Chair, 'color' | 'isComfortable' | 'invalid'>
/*
type NewChair = {
color: string;
isComfortable: boolean;
invalid: unknown;
}*/
keyof
To limit Union
to only lookup types in Type
, we first need Type
's lookup types. To do this, we can use the keyof
operator. It takes an object type and returns a string, string union, or number union of the type's keys.
type Chair = {
color: string,
legCount: number,
isComfortable: boolean
}
type ChairIndexUnion = keyof Chair // "color" | "legCount" | "isComfortable"
Now that we have both unions, we can constrain the Union
parameter to lookup types in Type
with the extends
keyword
type MyPick<Type, Union extends keyof Type> = {
[Key in Union]: Type[Key]
}
type NewChair = MyPick<Chair, 'color' | 'isComfortable' | 'invalid'>
/*
Type '"color" | "isComfortable" | "invalid"' does not satisfy the constraint 'keyof Chair'.
Type '"invalid"' is not assignable to type 'keyof Chair'.
*/
invalid
is not in Chair
, so the NewChair
type is invalid
Conclusion
Congratulations—you now have a typesafe pick function!
Reference
- Lookup Types/Indexed Access Types
- Mapped Types
- Generic Constraints
Top comments (0)