Implement `Pick` in TypeScript
Em dashes: 2
This type challenge is to create your own version of Pick. 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> = unkown;
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