How I improve my skills in Typescript #2 : Mapped Type
Tags: typescript, node, webdev, javascript
Published: 2023-04-03 14:43:34

I will share with us some tips that improved my skill in Typescript ! Today we will learn how to use `Mapped Type` !

## What is and why use Mapped Type ?

Using `mapped type` is usefull when :
>need to create a derived type from another type and keep both sync

Let's check an exemple to understand !

```typescript
type User = {
  name: string
  age: number
  userName: string
}

type UserPermissions = {
  canUpdateName: boolean
  canUpdateAge: boolean
  canUpdateUserName: boolean
}
```

Here we can see similar thing between `User` & `UserPermissions` type. Is there any solutions to **link** both type ? Because we *don't want* to add manually property in `UserPermissions` every time we add a new property in `User` type. 

The answer is `yes` and the solution is named `Mapped type` !

## Mapped type: The basics

### Create the same type

Before answering to the issue above, let's see how *Mapped type* is working !

We will create a type from `User` type, to begin let's create the **EXACT SAME** type.

```typescript
type User = {
  name: string
  age: number
  userName: string
}

type UserCopy = {
  [Property in keyof User]: User[Property]
}
```

If you check `Usercopy` type you will see this

```typescript
type UserCopy = {
    name: string;
    age: number;
    userName: string;
}
```

But why we are doing this, we create the same type as `User` ?!

I choose a basic example to see you how *Mapped type* is working. I will try to explain you what the f*ck is going on.

```typescript
type UserCopy = {
  [Property in keyof User]: User[Property]
}
```

First We are iterate each key in the `User` type with the `in keyof` operator *(frequently used when using mapped type)*, each key is contains in the variable `Property` (`'name'`, `'age'`, `'userName'`)

After this, we assign the value of each property with the dedicated value in `User[Property]`. Visually we are gonna something like:

1) Start : `[Property in keyof User]: User[Property]`

- Iterate to the first propery of User : `[name]: User['name']`, so we get the following typing `name: string`

- Iterate to the second propery of User : `[age]: User['age']`, so we get the following typing `age: number`

- Iterate to the last propery of User : `[userName]: User['userName']`, so we get the following typing `userName: string`

2) After iterating on each key, we get a new Type that is the same type as `User` !

### Edit value from type to type

We can also edit the property value type like :

```typescript
type UserCopy = {
  [Property in keyof User]: boolean
}
```

And get something like 

```typescript
type UserCopy = {
    name: boolean;
    age: boolean;
    userName: boolean;
}
```

We begin to see the **power** of Mapped type ! If we are adding a new property in `User` type, it will be automatically added to the `UserCopy` type without doing anything ! Don't forget, developer are Lazy ;)

## Mapped type: mapping modifier

Let's play with Mapped type from now !

We will use `mapping modifier` on mapped type.

If I need to create the folowing type `UserReadonlyProperty` that is the `User` type with `readonly` flag on each keys. I can create something like.

```typescript
type User = {
  name: string
  age: number
  userName: string
}

type UserCopy = {
  readonly [Property in keyof User]: User[Property];
};

// type UserCopy = {
    // readonly name: string;
    // readonly age: number;
    // readonly userName: string;
// }
```

Wow it's interesting ! We direclty assign `readonly` flag on each key ! We can also do the opposite thing ! We just need to use `-` before readonly to remove all readonly flag from type ~

```typescript
type User = {
  readonly name: string
  readonly age: number
  readonly userName: string
}

type UserCopy = {
  -readonly [Property in keyof User]: User[Property];
};

// We Get this 👇
type UserCopy = {
    name: string;
    age: number;
    userName: string;
}
```

This is called `mapping modifier` and we have another mapping modifier that I need to show you, the `Optional` mapping modifier (`?:`).

 ```typescript
type User = {
  name: string
  age: number
  userName: string
}

type UserCopy = {
  [Property in keyof User]?: User[Property];
};

// We Get this 👇
type UserCopy = {
    name?: string | undefined;
    age?: number | undefined;
    userName?: string | undefined;
}
```

As you can see we can add 'readonly' and 'optionnal' flag on key but we can also remove them ! By using the (`-`) flags: `[Property in keyof User]-?: User[Property]`

```typescript
type User = {
  name?: string
  age?: number
  userName: string
}

type UserCopy = {
  [Property in keyof User]-?: User[Property];
};

// We Get this 👇
type UserCopy = {
    name: string;
    age: number;
    userName: string;
}
```

And finally we can combine both 🔥

 ```typescript
type User = {
  name: string
  age: number
  userName: string
}

type UserCopy = {
  readonly [Property in keyof User]?: User[Property];
};

// We Get this 👇
type UserCopy = {
    readonly name?: string | undefined;
    readonly age?: number | undefined;
    readonly userName?: string | undefined;
}
```

## Mapped type: Key remaping with `as`

We see the `mapping modifier`, that is a very nice feature combined with mapped type ! So now let's go **more deeper** in `mapped type`

### Literals string

We can remaping key using literals string !

```typescript
type User = {
  name: string
  age: number
  userName: string
}

type RenameKey<Type> = {
  [Property in keyof Type as `canUpdate${string & Property}`]: Type[Property]
}

type UserCopy = RenameKey<User>

// We Get this 👇
type UserCopy = {
    canUpdatename: string;
    canUpdateage: number;
    canUpdateuserName: string;
}
```

Use `as` permits to change the key value and keep it in the new value !

Let's destructure the synthax step by step.

1) We iterate through `Type` key's, so we get `name`, `age` and `userName`.

2) In each iteration, we rename the key value as `canUpdate${Property}`, so we are getting

- `canUpdatename`
- `canUpdateage`
- `canUpdateuserName`

*2.5) If we pay attention on the synthax, we can see the `{string & Property}` synthax. Why are we needing to use `string` value here ? I will create a **dedicated article on this**, but to summarize it, we need to tell to TS that the key is a `string` and not a `symbol`, because we cannot use `symbol` in literals string (other key type like number of bingint will be corce to string)*

As you can see, remaping key is very powefull combined to mapped type. But there is a "little" issue here, we get `canUpdatename` not `canUpdateName`.

How can we achieve this ? 🧐

### Intrinsic string

To help with string manipulation, TypeScript includes a set of types which can be used in *string manipulation* and we are going to use it now in order to transform `canUpdatename` to `canUpdateName`.

```typescript
type User = {
  name: string
  age: number
  userName: string
}

type Copy<Type> = {
  [Property in keyof Type as `canUpdate${Capitalize<string & Property>}`]: Type[Property];
};

type UserCopy = Copy<User>

// We Get this 👇
type UserCopy = {
    canUpdateName: string;
    canUpdateAge: number;
    canUpdateUserName: string;
}
```

We do it ! Use `intrinsic string` allow us to Capitalize string value ! Here we capitalize the Key value from `User` type.

We can also use `intrinsic string` out of mapped type context.

```typescript
type LowercaseGreeting = "hello, world"
type Greeting = Capitalize<LowercaseGreeting> //Hello, world
```

There are other Intrinsic string [here](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#intrinsic-string-manipulation-types)

### Type utils

I repeat it again, but `Mapped type` are powerfull, we can rename each key from a type to create another type, we can use `intrinsic string` to manipulate string.

There is another things to do with them ;)

We can use Type Utils ! (if you don't know what is this, check my last article about Typescript tips or go [here](https://www.typescriptlang.org/docs/handbook/utility-types.html))

If we need to exclude a set of keys from a type to create a transformed type, we can use `mapped type` combined with Type Utils !!

```typescript
type User = {
  name: string
  age: number
  userName: string
}

type CopyWithoutKeys<Type, Keys> = {
  [Property in keyof Type as Exclude<Property , Keys>]: Type[Property];
};

type UserCopyWithoutNameAndUsername = CopyWithoutKeys<User, 'name' | 'userName'>

// We Get this 👇
type UserCopyWithoutNameAndUsername = {
    age: number;
}
```

I think you get the Mapped type synthax and concept. And of course we can use all features that we seen before !

## Bonus : Get all values type from a Mapped type

A bonus for all people that read until this section.

You can get all key's values type from mapped type (and type)

```typescript
type User = {
  name: string
  age: number
  userName: string
  isAdmin: boolean
}

type CopyWithoutKeys<Type> = {
  [Property in keyof Type]: Type[Property];
};

type UserCopy = CopyWithoutKeys<User>

type UserCopyValueTypes = UserCopy[keyof UserCopy] // string | number | boolean
```

Note that I added `isAdmin` key without adding it in `UserCopy` type, this is the magic of `Mapped Type` ;)

## Advanced concept with Mapped type

### Never as value in key/pair

Let's see what happened when you use Never as key/value with mapped type !

#### Never as key with mapped type

If you add a condition during key iteration when using mapped type with a `never` case, it will produce something cool

```ts
type isA<T> = T extends 'a' ? never : T

type To<T> = {
	[P in keyof T as isA<P>]: T[P]
}

type Toto = To<{ a: 1; b: 2; c: 3 }> // { b: 2; c: 3; }
```

When you set a key with the `never` value, it will be Omit from the final type !

#### Never as value with mapped type

As we seen before, using `never` can lead to some usefull tricks about mapped type, but what is happening if you are using `never` as value ?

```ts
type To<T> = {
	[P in keyof T]: T[P] extends never ? never : P
}

type Toto = To<{ a: 1; b: 2; c: never }> // {a: "a" b: "b" c: never;}
```

As you can see, `never` as value is not `Omit` like `never` as key.

Use `never` as value can be used to remove some key/value depending on a condition, so if you need to do this, use your condition at `key` level !

```ts
// type PropertyKey = string | number | symbol 
type isA<T extends Record<PropertyKey, unknown>, P extends PropertyKey> = T[P] extends 1 // Your condition
  ? never : P

type To<T extends Record<PropertyKey, unknown>> = {
	[P in keyof T as isA<T,P>]: T[P]
}

type Toto = To<{ a: 1; b: 2; c: 2 }> // {b: 2;c: 2;}
```

In the example above, we remove all key/value with a value equal to 1, we add the condition at the `key` level, so the `never` value will be automatically `Omit` !

### Union type with value

If you have any issue about this kind ot error 

```ts
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

const palette: Record<Colors, string | RGB> = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
};

const redComponent = palette.red.at(0); // Property 'at' does not exist on type 'string | RGB'.
```

You should [read my last article about `satisfies` operator](https://dev.to/codeoz/how-i-improve-my-skills-in-typescript-5-satisfies-operator-2312) !



---

I hope you like this reading!

☕️ You can [SUPPORT MY WORKS](https://ko-fi.com/codeoz) 🙏

🏃‍♂️ You can follow me on 👇

🕊 Twitter : [https://twitter.com/code__oz](https://twitter.com/code__oz)

👨‍💻 Github: https://github.com/Code-Oz

🇫🇷🥖 For french developper you can check my [YoutubeChannel](https://www.youtube.com/channel/UCC675U1ZUPFASsK9-FjawtA)

And you can mark 🔖 this article!