If it looks like a duck, walks like a duck, quack like a duck... All Things Typescript Newsletter - Issue #21
In this issue of All Things Typescript, we are going to explore Structural Typing in Typescript and it's benefits.
How do you test whether a bird is a duck? One approach would be to ask a series of questions, and the answers would dictate whether it’s a duck:
Does it look like a duck?
Does it walk like a duck?
Does it quack like a duck?
If the answer to all those questions is yes, then we can conclude it must be a duck. We are checking whether the bird in question models the behavior we expect from a duck, and if it matches up, we can then conclude it’s a duck with a certain level of certainty. This is popularly known as the duck test, and Typescript type checking operates similarly. This is known as structural typing or property-based typing.
Let’s take an example of this. Let’s take the following two types:
type Person = {
firstName: string;
lastName: string;
}
type PersonWithAge = {
firstName: string;
lastName: string;
age: number;
}
The difference between type Person
nd PersonWithAge
, is that the second one has an extra field: age
. What do you think happens if we tried to assign a variable of type PersonWithAge
to a variable of type Person
?
const personWithAge: PersonWithAge = {
age: 20,
firstName: "John",
lastName: "Doe"
};
const person: Person = personWithAge;
Nothing, Typescript doesn’t throw an error and is okay with that, but why? Typescript uses Structural Typing, and just like our duck test above, if all the properties required by the type of the variable it’s being assigned to are there and have the same type, Typescript and, by extension Javascript, doesn’t care how the object was created.
There are situations where Typescript will complain if you have excess properties and you can learn more about that here.
Understanding this and its implications is very important. For instance, Typescript doesn’t guarantee that an object will not have excess properties; as long as the required fields are present and with the same type as the corresponding field, Typescript will be okay, and it can lead to you sending excessive data to another system or exposing data you didn’t intend to.
Structural typing can also have surprising results. For instance, take the following class:
class Person {
firstName: string;
lastName: string;
constructor() {
this.firstName = "John"
this.lastName = "Doe"
}
}
What happens when we do something like this?
const personObj = {
firstName: "Jane",
lastName: "Doe"
}
const personClass: Person = personObj;
Typescript doesn’t complain even though we are assigning an object to a class. Why? This is because the object has firstName
and lastName
just like the class plus a constructor from Object.protype
. I won’t delve into this more in this issue, but you can learn more here.
One of the benefits of structural typing is when you are writing tests. Let’s use AWS DynamoDB DocumentClient Client as an example. If we wanted to save some items to the Database, we could pass a client whose type contains only properties of the DocumentClient class we are using:
function save(client: { put: () => {} }) {
// code here
}
This allows us to pass in the actual DocumentCliient from our code, but when testing, we can create a smaller mock object that we can pass in instead. We can even use the Pick
utility type to pick only the needed fields.
function save(client: Pick<DocumentClient, "put">) {
// code here
}
Here is another article that highlights the benefits of structural typing in testing.
Conclusion
In this issue of All Things Typescript, we looked at structural typing in Typescript and its benefits. By understanding structural typing in Typescript, you will have a better mental model of the type system and understand the behavior to expect from Typescript.
That’s it for today from me; If you liked this post, consider sharing it with your colleagues.
I hope you have a great week ahead and as always, keep on learning. Au Revoir 👋🏻!