How to correctly use TypeScript types for your API response

When you receive a JSON response you have no guarantee that it has the correct type. Sure you can typecast and pretend that everything is OK. But do you know how to be truly type-safe in this situation?

Vaclav Hrouda
5 min readJan 31, 2022
Photo by Hannes Wolf on Unsplash

Let’s start with a little more general topic. How can we check types? There are two approaches. We can either check types statically (compile-time, static analyzer) or at runtime.

Static type checking is strong enough in case we know our data in advance. But that’s not the case in most situations. Usually, our data comes from sources like API, a file or some kind of user input. Interface for these sources return a string and our responsibility is to parse it to the shape we expect in our program.

If we want to be 100% type-safe we have to analyze parsed data at runtime. Most of the type-safe languages combine static type checking with run-time checking. But what about TypeScritp?

TypeScript check types only at compile time. After compiling to JavaScritp there doesn’t stay any information about the type and because of that, we can’t trust that our dynamic data (eg. API response) have the form they are pretending they have.

Example time

Best way to demonstrate this behaviour is an example. The following example shows the usual situation of retrieving data from an API.

The first issue we encounter was the absence of code completion. We are using TypeScript so it should be easy to fix. We just need to define a type for our expected response.

And now we can just use type casting to unlock code completion.

It looks like we are done but we can still improve our code. Return type any of our fetchData function is not the best practice. We can change it to Article but that would make our function dependent on one specific return type. The ideal solution, in this case, is to use generics.

With this approach, we could also delete our typecast (as Article).

Now our code looks fancy and the functionfetchDatacan be used for any other endpoint. What if the API response will not match the type we have in our code? Do you think that our type system will warn us about that? The answer is NO. As I write in the introduction there is no type checking at runtime in TypeScritp.

How to check types at runtime?

Because the reflection is missing in TS we have to do it manually. For basic types like string or number it’s easy. We can write a simple if statement and we are done.

if (typeof myVariable === 'number') {
// do our stuff
}
if (typeof myVariable === 'string') {
// do our stuff
}

But objects are much more complicated. We have to check if the variable is object, perform null check, and for each property check its existence within object and do a separate type check.
The proper control for our Article would look like that.

Can you imagine doing that for more complex or even nested objects? A horrible idea it’s a pain to do that and it’s too error-prone. So how to do that and don’t shoot yourselves to the knee?

Type guards

One possible solution is to generate type guards. Type guards are normal functions but with a signature that tells TS that the parameter of the function has a specific type. The signature consists of two things. The function must return boolean and has return type of param is myType.
The following example demonstrates how are type guards used.

As you can see the implementation of the type guard is the same as the if statement in the previous example. The only useful solution would be to generate such type guards. But that has also many drawbacks. Besides the same issues as in the previous example according to me, the biggest problem is the synchronization with the type definition.

Infer TS types from runtime types

As we saw manual approaches are not practical enough to be widely used. The real key is hidden in the typeof operator. We can use it to infer TS types from runtime types.

One of the techniques to infer types is the use of type guards. We actually saw that in the example above.

The second and pretty similar method is the use of assertion functions. Those functions instead of returning boolean throws an error when the type of parameter is incorrect.

One benefit of that method is the exception and associated message so we know where is the issue. We also don´t need to write conditions when calling the function. But the return type of such function has to be void and as we will see later it can be useful to return something else.

Functional approach

We can use Either in type checking functions.

Now I want just to mention that such a thing is also possible. If you are not familiar with this concept you can continue reading without any worries.

Minimal implementation for basic types

A non-functional way would be to use exceptions and return the input parameter in case the type is correct. Minimal implementation for basic types can look like that.

Those examples are straightforward and don’t need much explanation. The type TypeGuard<T> is a type of those functions and will be used in the following examples more extensively.

This function basically checks if all items of the array has the type enforced by inner type guard.

This is the most complicated guard I will show here. It takes object of guards as an argument and returns a function that checks if all properties of that object have the correct type.

Usage

The functions above can be used in a very simple way.

It’s comparable to the syntax of the normal type definition but now we have types saved in the runtime world. And as I mentioned earlier we can get information from runtime to type world pretty effortlessly. We just have to use typeof operator and we are done.

type Article = ReturnType<typeof Article>

We can even use the same name for the type.

Type-safe way to fetch data

We can refactor our original example using the presented technique.

It is as simple as that.

A new superpower of types

Type guard can be any function. It means that your types can be defined be exactly as you want. Imagine you want to have a type that is always even number. No problem you just have to define your guard.

The limit here is just your imagination, but I would recommend being careful with creating some crazy types.

Conclusion

When we work with data from external sources we can’t be 100% sure that those data have the correct type. We can define TypeScript type, typecast our variable and hope that everything will work. But it’s just hope. We need a way to check types at runtime but TS doesn’t have elegant built-in tools for that. We can write a manual type guard but it has many drawbacks. To avoid the necessity to synchronize type definition and type guard we can define type guard first and use typeof operator to infer TS type from runtime definition.

Libraries

If you want a more robust solution than the one shown in this article here are some libraries with this functionality.

And that’s everything I have for you today. I hope you have found this useful. Thank you for reading

--

--