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!
}
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(),
})
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 Date
s 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]))
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> => {
// ...
}
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[]
... 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 :)
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>() => {...}
Hope that helps!
Shameless Plug
Liked this post?
I stream functional programming, TypeScript and Elm development every Tuesday at 10am on Twitch!
Top comments (0)