TypeScript - #01 - Pick
Tags: typescript
Published: 2022-07-30 07:04:00

Other languages: [Spanish](https://dev.to/devjosemanuel/desafio-typescript-pick-24fi)
Link to the challenge: [Pick](https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md)

---

In this challenge what we will pursue is to construct the `Pick<T, K>` type without making use of it. We will have to construct a data type that will allow us to extract the set of `K` properties of a `T` type. For example:

```tsx
interface Todo {
	title: string
	description: string
	completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
	title: 'Clean room',
	completed: false
}
```

---

## Solution

```tsx
MyPick<T, K extends keyof T> = {
	[P in K]: T[P]
}
```

---

## Explanation

We will start from the basic definition of the data type we want to construct where we will set the parameters we expect it to receive and we will also assume that it will initially return `any`:

```tsx
type MyPick<T, K> = any
```

But before we continue, let's stop for a moment to understand how `MyPick` should work. To do so, we are going to rely on the declaration of the `All` interface of the challenge definition by presenting three scenarios:

```tsx
MyPick<Todo, 'title'>
MyPick<Todo, 'title' | 'completed'>
MyPick<Todo, 'title' | 'completed' | 'invalid'>
```

How should `MyPick` behave in each of the three scenarios above?

- In the first case `MyPick` should not give an error since `title` is one of the attributes that are collected inside the `All` interface.
- In the second case we should not have an error either since the **union type** that is formed by `title` and `completed` corresponds to two of the attributes that are defined in `MyPick`.
- The third case is different since we are again in front of a **union type** but in this case the particularity is that one of the values that form it does not correspond to one of the attributes of the `All` interface so `MyPick` should inform us about it with an error message.

With this in mind it seems that the key to solving our problem is to somehow restrict the set of values that we can write to the second parameter of `MyPick<T, K>` or, in other words, to define `K` in such a way that it can only accept values that correspond to the attributes of `T`.

### `keyof`

Now how can we say in TypeScript that we want to extract the set of attributes that are associated with a type? The answer is thanks to the use of the `keyof` operator. 

To understand it better with our example, if we apply this operator to the interface `All` what we are going to obtain is a union type formed by the name of all the attributes that are defined inside the interface.

```tsx
keyof Todo ---> 'title' | 'description' | 'completed'
```

### `extends`

Now that we have the data type formed by all the attributes of the interface we have to restrict in some way that only one of them can be chosen and this is where the **conditional types** come into play.

Thinking in terms of set theory:

- What do we want to prove? That the set formed by the data types that is associated to all the attributes we want to extract is a subset of the set formed by the type attributes we are working with.
- Does TypeScript offer us a mechanism to be able to perform this check? Yes, and it is the use of the conditional types or, in our case, of the `extends` operator.

As we can guess `extends` is an operator that will allow us to perform checks in a similar way as we do JavaScript:

```tsx
A extends B ---> true whenever A is a subset of the elements of B.
A extends B ---> false otherwise.
```

Let's translate this idea into the scenarios we have shown above:

```tsx
B = keyof Todo ---> 'title' | 'description' | 'completed'

'title' extends B --> true 
'title' | 'completed' extends B --> true
'title' | 'completed' | 'invalid' extends B --> false
```

### Putting all the pieces together

Now we only have to join the two previous pieces to achieve our goal by establishing that the set of values that can be set in the `K` parameter must belong to the set of attributes that is collected in the `T` type. This leaves us with the following:

```tsx
MyPick<T, K extends keyof T> = any
```

### Returning the value

In the previous statement we are seeing that the result that is being returned is `any` and this is not what is intended in the challenge since what we want is to return an object that contains only the attributes of `T` that we have specified in the `K` parameter.

The way to achieve this is by using what is known as a **Mapped Type** which is nothing more than constructing a new type from the definition of another. 

Let's start by defining the simplest mapped type that we can think of to achieve our goal, which would be one that always returns a type without attributes as a result:

```tsx
MyPick<T, K extends keyof T> = {}
```

How do we now define the attributes of this object? Well, this is where we have to understand that both `T` and `K` are two types that will be available during the declaration of the type we are constructing (to some extent, if we think in terms of JavaScript, it is as if both types were linked to the scope of the declaration of our new type) and that, in addition, for the definition of the attributes we will be interested in the set that is contained in `K`. 

Is there a possibility to traverse all the values collected in `K` in TypeScript? The answer is yes, and it is thanks to the use of the `in` operator. Thinking in terms of arrays we could make a mental scheme like the following:

```tsx
K ---> 'title' | 'description' | 'completed'
P in K ---> ['title', 'description', 'completed']
```

where `P` is a type that will pass the `title`, `description` and `completed` values respectively while traversing the array.

And can we define an attribute within the mapped type from each of these array elements? The answer again is yes, using the notation between `[]` where  

```tsx
MyPick<T, K extends keyof T> = {
	[P in K]: ...
}
```

This, which is complex to explain in words, is best seen in the example we are following:

```tsx
interface Todo {
	title: string
	description: string
	completed: boolean
}

MyPick<Todo, 'title'> ---> { title: ... }
MyPick<Todo, 'title' | 'description'> ---> { title: ..., description: ... }
MyPick<Todo, 'title' | 'description' | 'completed'> ---> { title: ..., description: ..., completed: ... }
```

Now we only have to assign the data type that is associated to each of the attributes that we are extracting and this is achieved in a very simple way just using the operator `[]` on the data type indicating which is the element from which we want to extract the information of the type (analogously to how we can do it in TypeScript to access each of the elements of an object).

But how do we know which is the concrete attribute? Well, we have to remember again that all the generic types that we use during the declaration of our type can be used in the rest of its definition. 

In our case, as we have declared something like `[P in K]` to go through all the attributes that are collected within the set of keys to extract `K` and as we also have the certainty that these keys will belong to the type on which we are working `T` we can simply use `T[P]` to obtain the type associated with that attribute:

```tsx
MyPick<T, K extends keyof T> = {
	[P in K]: T[P]
}
```

If we go back to our example we would get something like the following:

```tsx
interface Todo {
	title: string
	description: string
	completed: boolean
}

MyPick<Todo, 'title'> 
	---> { title: string }
MyPick<Todo, 'title' | 'description'> 
	---> { title: string, description: string }
MyPick<Todo, 'title' | 'description' | 'completed'> 
	---> { title: string, description: string, completed: boolean }
```

---

### Links of interest

- [`extends` Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#handbook-content)
- [`in` Operator](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing).
- [`keyof` Type Operator](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#handbook-content)
- [Mapped Types](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#handbook-content)
- [`Pick` Utility Type](https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md)