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#

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#