Understanding the TypeScript satisfies Operator
Unraveling the satisfies operator in TypeScript 4.9 – its purpose explained on a real-world example.
As per official documentation, Typescript satisfies
operator was introduced in the TS 4.9 to solve the common dilemma:
How to ensure that some expression matches some type, but also to keep the most specific type of that expression for inference purposes?
For a while, even with an understanding of its theoretical benefits, I found the practical application of the satisfies operator elusive. Each time I encountered a potential use case, I hesitated, often revisiting documentation or seeking guidance from online articles to confirm my approach.
I decided to develop a hands-on example that finally clarified the concept for me.
The problem
Imagine we have the following Person
type defined in our code:
and our Address
looks like this:
Ideally, in most cases, we would prefer to define our types more precisely to have a more concrete structure to work with. However, for the purposes of this example, let's work with this broader type definition.
We can then create an object that conforms to the Person
type by using the :<Type>
type annotation. This approach not only provides automatic code suggestions as we type but also ensures that we include all required properties.
So far, nothing surpring here. Here's the thing, though: what happens when we try to access a property that doesn't really exist on this particular object?
At first, it seems like everything's in order. The postalCode
variable is typed as a string. No red squiggly line in sight. But why is it working even when the postalCode
property hasn't been defined on the object? Well, this occurs because person.address
is of the Record<string, string>
type. Such a broad type definiton allows us to access any property, real or imagined, without TypeScript raising errors.
However, the real problem surfaces at runtime: even though Typescript suggests that postalCode
variable is of string
type, when we actually try to run the code, we'll see that the variable is undefined
. This unexpected behavior can lead to unforeseen errors, potentially causing the entire application to crash.
Surprisingly, it turns out that our code is not as type-safe as we might have assumed.
This same issue also arises when using an explicit type assertion:
Solution
Type inference
We always have the option to create our object by relying solely on type inference.
This way, we won't be able to access non-existent properties:
but thanks to the Typescript's structural typing, it would be sufficient, for example, to be able to pass it down to a function that expects its argument to be of Person
type:
However, this approach also means we lose the ability to verify if the object we're creating is of the correct type until it's used in a specific type-restricted context.
If we make a typo:
we might not even realize it until we attempt to pass this object to a function that expects an argument of the specific type:
Surely, this is not a satisfactory solution we can settle for.
Satisfies
This is where the satisfies
operator comes to the rescue:
What happens if we try to access person.address.postalCode
now?
We got a type error! Now, TypeScript understands that person.address.postalCode
does not exist on the object we've just created. Simultaneously, it also recognizes that person
correctly conforms to the Person
type, along with all its associated implications.
As we can see in this example, we get all the benefits of explicit type annotation and type inference in one handy operator!
Summary
To slightly adapt the quote introduced at the beginning of this article, I find it convenient to think of the satisfies
operator in the following way:
The
satisfies
operator combines the detailed type inference and the ability to ensure strict type conformance.
Having grasped this concept, its utility becomes evident, especially in scenarios involving broad types. With the satisfies
operator, we can retain the necessary flexibility these types offer while simultaneously upholding strict type safety.