DEV Community

Cover image for Type Constraints In TypeScript
Gio
Gio

Posted on

Type Constraints In TypeScript

Photo credit: https://unsplash.com/photos/ar2dUoleWYA


Suppose you're a back end API developer and you need a way to guarantee at compile time that you're only sending json-serializeable data out of your API.

You have a send function that takes some data and sends it to a API consumer.

const send = <T>(data: T): Promise<null> => {
  // magic...
  // or more realistically
  // calls express' res.json() method!
}
Enter fullscreen mode Exit fullscreen mode

And you're trying to prevent a dev from trying to send the following out of your api:

send({
  makesNoSense: () => { console.log('woops!') },
  myDate: new Date(), 
})
Enter fullscreen mode Exit fullscreen mode

The above would be stringified (i.e. serialized) under the hood into { myDate: 'iso-date-string' }. Functions aren't part of the JSON spec and thus would be removed entirely. And Dates are automatically cast to strings which is not a very efficient way of sending timestamps down the network (hint: you want unix timestamps in the form of an integer).

Woops! Looks like a dev forgot to call a function and also forgot to call Date.getTime 😭

So how do we prevent this sort of thing?

Type Constraints To The Rescue

A type constraint is a "rule" that narrows down the possibilities of what a generic type could be.

For example, in the the send definition above, we declared a type variable T that is not constrained at all. Which is why we were able to call send with values that aren't JSON serializeable.

// This compiles ... API would send `{}`
send(new Set([1,2,3]))
Enter fullscreen mode Exit fullscreen mode

We can thus narrow the possibilities of the generic type T to allow allow JSON values as follow:

const send = <T extends JSONValues>(data: T): Promise<null> => {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

The only difference is that now we've appended extends JSONValues to the type variable declaration for T.

In plain english T extends JSONValues translates to, "T is a generic type that must conform to the definition of JSONValues".

What's JSONValues?

It's defined as this:

type JSONValues
    = number
    | string
    | null
    | boolean
    | { [k: string ]: JSONValues }
    | JSONValues[]
Enter fullscreen mode Exit fullscreen mode

... Yup, this is the entire JSON specification in 7 lines of code! 🤯

Now, if I call send(new Set([1,2,3])) I will get a type error. Whaaaat?!?!

Now you can guarantee at compile-time that you will only send valid data to your JSON API consumers :)

Live demo

Conclusion

Type constraints are a very powerful way to supercharge your typescript codebases.

For each generic type variable that you'd like to constrain, you would append the extends SomeTypeName to the definition. Example:

const myFn = <T extends JsonValues, U extends FinancialData>() => {...}
Enter fullscreen mode Exit fullscreen mode

Hope that helps!


Shameless Plug

Liked this post?

I stream functional programming, TypeScript and Elm development every Tuesday at 10am on Twitch!

https://www.twitch.tv/vimboycolor

Top comments (0)