Better types in TypeScript with type guards and mapping types
Tags: javascript, typescript, webdev, node
Published: 2022-03-23 12:23:17

TypeScript gains more and more popularity among the Javascript developers, becoming even a stardard when it comes to nowadays software development and replacing to some extent Javascript itself. 

Desipte the main goal of this language is to provide type-safety programing in to the chaotic Javascript, many people use it just because that’s the trend. In that case the only feature they use is revealing the types of given values, and if they can’t type something, an `any` is being used instantly.

Well… TypeScript is so much more. It provides many features, so let’s focus on the ones that will help you with type organizing as well as bringing more security to your code.


## A brief story of any
If you used TypeScript, it’s likely that you have been using `any` so much. This type is quite uncertain one and can mean... everything.. literally everything.

When you type something by `any` is same as you would say _“I don’t care about what the type is here”_, so you essentially ignore the typing here as if you were use plain Javascript.

For that reason `any` should not (almost) never been used, because you ignore the typing, the thing that TypeScript was actually built for!

You may raise a question _“ok, but what if I totally don’t know what the type is?!”_. Yeah, in some cases you really don’t know it, and for that is better to use `unknow` over the `any`. 

The `unknow` type is very similar to `any` - also match to everything, except one thing - is type-safe. Considering an example:

```typescript
let x: unknown = 5;
let y: any = 5;

// Type 'unknown' is not assignable to type 'string'.
let x1: string = x;

// fine.
let y1: string = y; 
```

As you can see, when you use `unknown` in the context of string, the TypeScript doesn’t allow me to do this, because they are different types, while with `any` I can do whatever I want.

That’s why `any` is very insecure. Using `any` makes your code prone to even crash as you are using one data in the context of different.

Does it mean I can’t use `any`? No, `any` has its own purpose, I will show you later. In terms of typing function arguments, return values, type aliases etc. - stay with `unknown`.

## Protection with type guards

This is really important feature of TypeScript. It allows you to check types in your code to assure that your data-flow relies on the correct data types. Many people use it, without even knowing that it’s named _“type guards”_. Let’s go with examples.

```typescript
function product(x: number) {}
function discount(x: string) {}

function cart(x: string | number) {
 // Argument of type 'string | number' is not assignable to parameter of type 'number'.
 product(x);
 // Argument of type 'string | number' is not assignable to parameter of type 'number'.
 discount(x);
}
```

What’s happening here? We have function `cart` that takes one argument which can be either `string` or `number`. Then we call two functions, each requires also one argument, first (`product`) `number` second (`discount`) `string`. For both functions, the argument from cart has been used - why does TypeScript raise an error?

Well, TypeScript basically has no clue what you want to do. We are giving `string` or `number` then use it in a different contexts - once just `number` then just `string`. What if you pass string to the function `product`? Is that correct? Obviously not - it requires a different type. The same with function discount. That’s the issue here.

We must sift somehow possible types, to make sure, we have the right one in the given context. This is the goal of type guards - we make protection in given line against passing incorrect types.

#### typeof checking

In this particular case, a `typeof` guard is completely enough:
```typescript
function cart(x: string | number) {
 if (typeof x === 'number') {
  product(x);
 }
 if (typeof x === 'string') {
  discount(x);
 }
}
```

Now, everything receives the correct types. Worth notice, if we put return statement inside of first “if” then second if is no longer needed! TypeScript will catch the only one possibility is there.

#### The object complexity

How about more complex types? What if we have something more sophisticated than primitives? 

```typescript
type Product = {
 qty: number;
 price: number;
}

type Discount = {
  amount: number;
}

function product(x: Product) {}
function discount(x: Discount) {}

function cart(x: Product | Discount) {
 // Argument of type 'Product | Discount' is not assignable to parameter of type 'Product'.
 product(x);
 // Argument of type 'Product | Discount' is not assignable to parameter of type 'Product'.
 discount(x);
}
```
We have here the same scenario as in the previous example, but this time we have used more complex types. How to narrow down them? 

To distinguish _"which is which"_ we can use `in` operator and check whether the certain fields are present in the object.

For instance, our `Product` has `price` while the `Discount` has `amount` - we can use it as differentiator.

```typescript
function cart(x: Product | Discount) {
 if ('price' in x) {
  product(x);
 }

 if ('amount' in x) {
  discount(x);
 }
}
```
Now, again TypeScript is satisfied, however, is that clean enough?

#### Customized type guards
A previous solution may solve the problem and works pretty well… as long as you not emerge more complex types - having sophisticated `in` clause won’t be so meaningful - so what can we do?

TypeScript provides a `is` operator that allows you to implement special kind of function you can use as type guard.

```typescript
function isProduct(x: Product | Discount): x is Product {
 return 'price' in x;
}

function isDiscount(x: Product | Discount): x is Discount {
 return 'amount' in x;
}

function cart(x: Product | Discount) {
 if (isProduct(x)) {
  product(x);
 }

 if (isDiscount(x)) {
  discount(x);
 }
}
```
Look at the example above. We could create a checker-functions that bring capability to confirm the input type is what we expect.

We use statement of `is` to define, a function which returns `boolean` value that holds the information if the given argument acts as our type or not.

By using customised type-guards, we can also test them separately and our code becomes more clear and readable.

## The configuration is tough…

Agree. The configuration of TypeScript is also quite complex. The amount of available options in a `tsconfig.json` is overwhelming.

However there are bunch of them that commits to the good practices and quality of the produced code:

- **strict **- strict mode, I would say that’s supposed to be oblibatory always, it forces to type everything
- **noImplicitAny **- by default, if there is no value specified, the `any` type is assigned, This option forces you to type those places and not leave `any` (eg. function arguments)
- **strictNullChecks **- the `null` and `undefined` are different values, you should keep that in mind, so this option strictly checks this
- **strictFunctionTypes **- more accurate type checking when it comes to function typings

Obviously there are more, but I think those ones are the most important in terms of type-checking.

## More types? Too complex.

Once you project grow, you can reach vast amount of types. Essentially, there is nothing bad with that, except cases when one type was created as copy of the other one just because you needed small changes.

```typescript
type User = {
 username: string;
 password: string;
}

// the same type but with readonly params
type ReadOnlyUser = {
 readonly username: string;
 readonly password: string;
}
```

Those cases break the DRY policy as you are repeating the code you have created. So is there any different way? Yes - mapping types.

The mapping types are build for creating new types from the existing ones. They are like regular functions where you take the input argument and produce a value, but in the declarative way: a function is generic type and its param is a function param. Everything that you assign to that type is a value:

```typescript
type User = {
 username: string;
 password: string;
}

// T is an "argument" here
type ReadOnly<T> = {
 readonly [K in keyof T]: T[K]
}
type ReadOnlyUser = ReadOnly<User>
```
In the example above, we created a mapping type `ReadOnly` that takes any type as argument and produces the same type, but each property becomes readonly. In the standard library of TypeScript we can find utilities which are built in exactly that way - using mapping types.

In order to better understand the mapping types, we need to define _operations_ that you can do on types.

#### keyof
When you use a `keyof` it actually means _“give me a union of types of the object key’s”_. For more detailed information i refer to the official documentation, but for the matter of mapping types when we call:

```typescript
[K in keyof T]
```

We access the _“keys”_ in the object `T`, where each key stays under the parameter `K` - Sort of iteration, but in the declarative way as `K` keeps the (union) type of keys, not a single value.

As next, knowing that `K` has types of each parameter in a given object, accessing it by `T[K]` seems to be correct as we access the _“value”_ that lives under the given key, where this key also comes from the same object. Connecting those statements together:

```typescript
[K in keyof T]: T[K]
```

We can define it: _“go over the parameters of the given object `T`, access and return the value that type `T` holds under given key”_. Now we can do anything we want with it - add readonly, remove readonly, add optional, remove optional and more.


#### The “if” statements

Let’s assume other example:

```typescript
type Product = {
 name: string;
 price: number;
 version: number; // make string
 versionType: number; // make string
 qty: number;
}

// ???
type VersioningString<T> = T;
type X = VersioningString<Product>
```

We have type `Product` and we want to create another type that will change some properties to string, let's say the ones related to version: `version` and `versionType`.

We know how to _“iterate”_ but we don’t know how to _“make a if”_. 

```typescript
type VersioningString<T> = {
 [K in keyof T]: K extends "version" | "versionType" ? string : T[K]
};
```

We can put the _“if”_ statements in that way by using extend keyword. Since that’s declarative programming, we operate on the types we are checking if our `K` type extends… the union type of _“version”_ and _“versionType”_ - makes sense? In this meaning we check the inheritance of given type, just like among the classes in oriented programming.

#### Type inferencing

TypeScript always tries to reveal the types automatically and we can access it and take advantage of revealed type.

It’s quite handy when when it comes to matching something by `extend` keyword and obtain the inferenced type at the same time.

```typescript

type ReturnValue<X> = X extends (...args: any) => infer X ? X : never;

type X1 = ReturnValue<(a: number, b: string) => string> // string
```
This is classical example of obtaining the return type of given function. As you can see, by using `extend` we can check whether input arg (generic) is a function by its signature, but in that signature we also use `infer` keyword to obtain of what return type is, then save it under the `X` field.

----

### Connecting all of pieces together - A real world scenario

Using those mechanics, let’s now break down the following type:

```typescript
type CartModel = {
 priceTotal: number;
 addToCart: (id: number) => void
 removeFromCart: (id: number) => void
 version: number;
 versionType: number;
}
```

Our goal is to create a new type that, skips fields related to versioning and adds quantity argument to both `addToCart` and `removeFromCart` methods. How?

Since there is no simple declarative operations of skipping fields, we need to implement it in the other way. We know that it's feasible to create a new type from existing one by going over the fields of it, however we exactly want to limit those fields.

```typescript
type SingleCart <T> = {
  // keyof T ??
  [K in keyof T]: T[K]
}

// prints all fields as normal
type Result = SingleCart<CartModel>

// goal:
type SingleCart <T> = {
  [K in <LIMITED VERSION OF FIELDS OF T>]: T[K]
}
```

How can we achieve that? Normally to access all of the fields we use `keyof T` but we our goal is to limit te list of possible keys of T.

Since the `keyof T` gives us a union of the fields, we can limit this by using extend keyword:

```typescript
// access all of the fields
type R1 = keyof CartModel

type SkipVersioning <T> = T extends "version" | "versionType" ? never : T

// gives union of "priceTotal" | "addToCart" | "removeFromCart"
type R2 = SkipVersioning<keyof CartModel>
```

So now we can use that type:

```typescript
type SkipVersioning <T> = T extends "version" | "versionType" ? never : T

type SingleCart <T> = {
  [K in SkipVersioning<keyof T>]: T[K]
}

/*
it gives a type: 
type ResultType = {
   priceTotal: number;
   addToCart: (id: number) => void;
   removeFromCart: (id: number) => void;
}
*/
type Result = SingleCart<CartModel>
```

We have just removed fields related to the version!

The next part is adding a `quantity` argument to functions in the type. As we already have access to the type of given field (`T[K]`), we need to introduce another one for transforming if given type is function:


```typescript
type AddQuantityToFn<T> = ... // ??

type SingleCart <T> = {
  [K in SkipVersioning<keyof T>]: AddQuantityToFn<T[K]>
}
```

The `T[K]` is being wrapped by a new type `AddQuantityToFn`. This type needs to check whether given type is a function and if that's true, add to this function a new argument `quantity` if not, don't do anything. The implementation may look as follows:

```typescript
type AddQuantityToFn <T> = T extends (...args: infer A) => void ?
  (quantity: number, ...args: A) => void
  :
    T
```

If the type is a function (`extends (...args: infer A) => void`), add a new argument `quantity: number` (returns a type of `(quantity: number, ...args: A) => void`) if not, keep the old type `T`. Please notice we are using also type inferencing (`infer A`) to grab the old argument's types.


Below, full implementation of it:

```typescript
// Skips properties by given union
type SkipVersioning <T> = T extends "version" | "versionType" ? never : T

// Adds new argument to the function
type AddQuantityToFn <T> = T extends (...args: infer A) => void ?
 (quantity: number, ...args: A) => void
 : T

// Goes over the keys (without versioning ones) and adds arguments of quantity if that's method.
type SingleCart <T> = {
 [K in SkipVersioning<keyof T>]: AddQuantityToFn<T[K]>
}

type ResultType = SingleCart<CartModel>
```

Quick summary: First of, we have defined a type that generates for us a union of property names besides ones related to versioning. Secondly, type for creating a new argument - if the type if function - if not, return given type. Lastly, our final type that goes over the keys (filtered) of an object, and adds arguments to the method (if needed).


## Recap

TypeScript might be difficult and helpful at the same time. The most important thing is to start using types in a wise way with an understanding of how they work and with a right configuration that will lead you to produce properly typed code.

If that's something overwhelming for newcomers, would be nice to introduce it gradually and carefully and in each iteration provide better and better typings as well as type guarding of your conditional statements.