Notes on Advanced TypeScript: Transforming Types
Tags: react, typescript, types
Published: 2022-04-28 14:31:01

---
title: Notes on Advanced TypeScript: Transforming Types
published: true
description: Notes on Advanced TypeScript
tags: react, typescript, types
series: Notes on Advanced TypeScript
---

### Introduction

These notes should help in better understanding advanced `TypeScript` topics and might be helpful when needing to lookup up how to leverage TypeScript in a specific situation. All examples are based on TypeScript 4.6.

### Transforming Types

There are situations where you have a defined type, but need to adapt some properties to work for a specific use case. Let's take the following example, where we have defined a `Box` type:

```js
type Vec2 = { x: number; y: number };
type Box = {
    id: string;
    size: Vec2;
    location: Vec2;
    content: string;
    color: string;
};
```

This `Box` type works well, except that we have a user interface, that allows the user to define the size, content, color and even location. The `id` property might not be defined yet, which prevents us from using the type as is. We need a way to tell our input, that the provided values are a `Box` with the `id` property being optional.
The following example will not work:

```ts
const defineBox = (box: Box) => {
   // some processing happening here
};
defineBox({
  content: "Content goes here",
  color: "green",
  location: {x: 100, y: 100},
  size: {x: 50, y: 50}
}); 
/** 
 * Fail: Property 'id' is missing in type 
 * '{ content: string; color: string; location: { x: number; 
 * . y: number; }; size: { x: number; y: number; }; }' 
 * but required in type 'Box'. 
 */
```

TypeScript will complain that the property `id` is required in type `Box`. The good news ist that we can transform our `Box` type to work via defining our own `MakeOptional` type. By leveraging the built-in types `Pick` and `Omit` we can create a type that accepts a defined of keys that we can be converted to optional:

```ts
type MakeOptional<Type, Keys extends keyof Type> = 
  Omit<Type, Keys> & Pick<Partial<Type>, Keys>;
```

Let's take a closer look at what is happening. First we use `Omit` to remove any keys from the original type and then we make our type partial via the `Partial` type and pick the previously excluded keys. By joining the two type operations, we can now use the newly created `MakeOptional` in our previous example.

```ts
type BoxIdOptional = MakeOptional<Box, "id">;
const defineBox = (box: BoxIdOptional) => {

};
defineBox({
  content: "Content goes here",
  color: "green",
  location: {x: 100, y: 100},
  size: {x: 50, y: 50}
});
```

Our `defineBox` function works as expected now, no matter if the `id` is provided or not. This good already, but we can do even more type transformations as needed. Let's look at a couple of more scenarios.
We might want to convert all properties by type, for example we would like to convert all properties of type `string` to `number`. This can be achieved by defining our own `ConvertTypeTo` type:

```ts
type ConvertTypeTo<Type, From, To> = {
  [Key in keyof Type]: Required<Type>[Key] extends From ? To : Type[Key];
};
```

By going through all the keys, we check if a key extends the `From` generic type and convert it to the defined `To` type.

```ts
/**
 * type BoxStringToNumber = {
 *   id: number;
 *   size: Vec2;
 *   location: Vec2;
 *   content: number;
 *   color: number;
 * }
 */
type BoxStringToNumber = ConvertTypeTo<Box, string, number>;
```

By using the `ConvertTypeTo` type, we converted all properties of type `string` to `number`.
Another scenario might be that we want to include or exclude properties by type. Here we can write a building block type that can extract property keys based on a type.

```ts
type FilterByType<Type, ConvertibleType> = {
  [Key in keyof Required<Type>]: Required<Type>[Key] extends ConvertibleType ? Key : never;
}[keyof Type];
```

Again, we iterate over all the keys for a given type and check if the key extends the type we want to filter on. Any key that does not extend the `convertibleType` is filtered out by returning `never`.
A short `FilterByType` test using our previously defined `Box` shows that we can retrieve all keys of type `string`.

```ts
// type BoxFilteredByTypeString = "id" | "content" | "color"
type BoxFilteredByTypeString = FilterByType<Box, string>;
```

Now that we have our `FilterByType` in place we can write a custom type that either includes or excludes properties by type. To exclude we can use `Omit` again and combine it with our custom type:

```ts
type MakeExcludeByType<Type, ConvertibleType> = 
  Omit<Type, FilterByType<Type, ConvertibleType>>;
```

To include all properties by type, we only need to replace `Omit` with `Pick`:

```ts
type MakeIncludeByType<Type, ConvertibleType> = 
  Pick<Type, FilterByType<Type, ConvertibleType>>;
```

Here is an example showing how we can convert the `Box` type, by including or excluding all properties of type string.

```ts
/**
  type BoxOnlyVec2 = {
    size: Vec2;
    location: Vec2;
  }
 */
type BoxOnlyVec2 = MakeExcludeByType<Box, string>;

/**
  type BoxOnlyNumber = {
    id: string;
    content: string;
    color: string;
  }
 */
type BoxOnlyNumber = MakeIncludeByType<Box, string>;
```

There are more transformations we can do, like for example making properties required, optional or read only based on a type or types. [Here are more examples you can checkout](https://www.typescriptlang.org/play?#code/C4TwDgpgBAwg9gWzAV2BAPAFXBAfFAXigG8oBtAaSgEsA7KAawhDgDMptIBdALg50pcoAXygB6MVGAALAIa0GAZygABAIK0AJrIBO1RcABKsplFZwdU6dGDUwAQgBQj0JCgBZExADyYW3FpZABssHAAaKApmZQgADzQtZSYWdk48QlhEFDR0bwRqYFDICKiQRXwAMkcoGqgABWoAYwZ0Ot1bYKK8EujcXABuZ1doeFoANwgdYDTMOC6IgDEdRAjZ-CJiavJSmnpktn5uPkMIAEdkah0ITS7cSmYhOITNZSXEKAB+DjgoPjT7kBcQbCQYuHBQBbUIJoHQAIRAaXmmXGk1sACMghA0usSFsAbtGMwDidzpdrrdeFASRcrjdsQDHvEIIlkRMptQMVjwV8dnxaBA2cCyPtUjggUNwZ4mABRWKNILITQQeGItIRUZs9GY7EZPIFJGQ6GTFU4JEa1Ec7U4Pqg4YeLwASVo8sVyoRprVrItnJ1RAazQNUJhJsgZoCmstXMgNolbilEBOsk03loQRAIYwnvN7J91oy8CQqAw8dlLqVGbDKJzVujUAqVIgSYCafQ8adZbdqvCXurUbwMbBca8vn8gSCFaz4e9NfSRAL2QwbXZnTbzoV5fdocnVa1fb6dftMrl687Hu72d32IGseg8epZM0E-PU97vsyhZyJePrqfxR7l7zet71pVtHTXH9N0zZ8d0jK9r0cCQoFlWQkExQdoAANQgRoACYMlIWI+WQBA0UmfooBAIiSMmERBjtWE4FifCthqahND4Aw9FoABzQZaigRRqAALwgPgsNwvjaiCOBGlkUcxOwnDJJqRoAgSYAOOALjeJYqBVOknRNO04FQTEAAqMytnoxiAGUtLobjvB0AA5YjSMsDZdLYoyHKgAAfKBaDcsjdMEkSqPc5SoGk2T5KgcSlN01TaHUnyeP8wLgp0KL9IsNLuIyoLqOyrZhGqMyxHQqAGNiOztKc1zivzF9phwWZ0BqiJOIcrr7PSgKivc+DzMsmprNiFM0wS5j+LC0T4sUqKYrk6gAgUiTSvKyrxsmkBpqIL8OwrTqBL67jhosqzwRq3bGvcmbam807jKStTmQ056HJyuADPyySyqgCqqpu1MQDumiDrAo7II6xjeu0+CEMuqRrsYxNk1BurfM82bhPmhKlpkla1oWjb+KuJtQZodjPp4qKKc0ZsQD0t6Ur+3SGaZlnftpnSAaB8b0d2rH0shpghdB464d5i7RpRtwaoAdQKaQR1Wsd9txXGRI+da8IC5AtAgVg6GuQnYvV3XSf1qBDaVE3+U0KKnu6unXpS972f43LDN5-6tuBxjlZkNWAmCTX41DscpdiCIEtlq6FaDlWRe4xQ6mWSB2QgRRgOuB6ajmvXzeJ2hi68mnXZ073WY+qvvp5+vHH57bUdiYPpFT9PM4tHO880DI7zOGlrgrTZ+Jds6oqL62S7ign3fUq2m5rgzl6n5v4YchP5egGqHWTPx1eCAuWY9tm-dexuN-45b58W0K8fLifNHX7SMrt43Tad6oW8D2ID5RxPmLHwR8w4hBOgAIjYpA68QA)

We should have a basic ideas of how to transform types now.

---

If you have any questions or feedback please leave a comment here or connect via Twitter: [A. Sharif](https://twitter.com/sharifsbeat)