Fixing a Promise return object to be typed in Typescript
Tags: typescript, javascript, webdev
Published: 2022-07-01 12:37:18

If you decided to use Typescript as language to build a web application, you probably want to call REST API to get objects. A problem may occur when you choose the `fetch` standard: Promises doesn't really produce typed objects. But, there are solutions :smile:

## The problem

Let's image you create this class that represent a user:

```typescript
class User {
   username = ""
   logout() {
      console.log("Log out!");
   }
}
```

> You see? There is a method to log out. 

So, you say "Typescript uses typed variables, let's go to get the user from a REST API and the magic will happen"

```typescript
class Api() {
  getUser(name: string): Promise<User> {
    // the problem is here
    fetch(`/api/v1/user/${name}`).then((response) => response.json())  
  } 
}
```

![It's a trap](https://media0.giphy.com/media/Z1LYiyIPhnG9O/giphy.gif?cid=ecf05e4741l2bh6aoczbrrm74h08z2oxoj05miqrfi7o8zue&rid=giphy.gif&ct=g)

The error here is to think that Typescript will cast the JSON object to a `User`. Even if `response.json()` returns `any` type. But…

> **You're wrong**.

Try!

```typescript
const api = new Api()
api.getUser("Franck Lambert").then((user: User) => {
 console.log("user", user); // shows a good structure
 user.logout(); // ERROR !
})
```

That's a trap, I said. Even if you use typed variables, that's a JavaScript execution that's behind the scene. And TypeScript doesn't change the object type.

To understand the reason that Typescript will not create a `User`, just ask: 

> "how should it transform an anonymous object to a complete `User` object? What if I need to use a constructor, for example?"

In our case, there is no constructor, our class is very simple, and we can apply the JSON object to our `User`. So… maybe…

Maybe we can automate the construction.

There are several ways to do, but I will show you:

- a quick and dirty method
- a complex method


## The `quick and dirty` solution

This method is a simple "map all to object" call. Because we don't need construction, and because it's a simple class to transport information and methods, the following can be used.

We only need to "copy" the plain object to a typed object. Of course, what you can do it like this…

```typescript
fetch(`/api/v1/user/${name}`)
   .then((response) => response.json())  
   .then((user: User) => {
      const u = new User();
      u.username = user.username;
      return u;
   });
```

But, let's imagine a more complex class with a lot of properties to change. And what if we add properties in the class? No… really, we require a more automated method.

> And... there is one!
> `Object.assign(target, source)` returns the `target` element with all `source` applied to it. As the `target` is typed, the return object is typed too.

```typescript
fetch(`/api/v1/user/${name}`)
   .then((response) => response.json())  
   .then((user: User) => Object.assign(new User(), user))  
```

That's *quick and dirty*, but that is not so bad.

And if the API returns a list of object:

```typescript
fetch("/api/v1/users/")
   .then((response) => response.json())
   .then((users: Array<User>) => users.map(
       (user) => Object.assign(new User(), user))
   )  
```

## The complex method

There is a very nice module that you can use: 
{% embed https://github.com/typestack/class-transformer %}

`class-transformer` provides a `plainToClass()` function that transforms your plain objects (from JSON response, for example) to a typed object.

You will be able to do:
```typescript
import { plainToClass } from 'class-transformer';

fetch(`/api/v1/user/${name}`)
   .then((response) => response.json())  
   .then((user: User) => plainToClass(User, u));
```

This is OK, and it works with a complete check. So, if you have a lot of objects to treat, and you need to ensure the return type, use this module.

## Is it correct?

It is for simple objects. When the class has no properties which are object instance. This only will **not** work on inherited classes. So you can use `Object.assign` when you get simple objects from API.

You'll need more work to fix embedded objects, and sometimes you'll need to build the object yourself without automation.

There is no magic, you need to develop.

If you require a lot of control, the `class-transformer` module is very efficient and is well-supported in all frameworks I use (Vue, Angular, etc.) in Typescript.

Hope that my article helps you.