DEV Community

Jacob Asper
Jacob Asper

Posted on

Implement `Pick` in TypeScript

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> = /* ... */
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Note that only types can be used for indexing

const key = "color"
Chair[key] // Type "key" cannot be used as an index type
Enter fullscreen mode Exit fullscreen mode

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;
}
*/
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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;
}*/
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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'.
*/
Enter fullscreen mode Exit fullscreen mode

invalid is not in Chair, so the NewChair type is invalid

Conclusion

Congratulations—you now have a typesafe pick function!


Reference

Top comments (0)