Getting started with the TypeScript satisfies operator

TypeScript is a programming language that is a superset of JavaScript. It adds optional static typing, classes, and interfaces to JavaScript. TypeScript compiles to readable, standards-based JavaScript. One of the benefits of TypeScript is that it enables IDEs to provide a richer environment for spotting common errors as you type the code. According to, the satisfies operator is a feature released in TypeScript v4.9. It allows developers to assign the most specific type to expressions for inference. The satisfies operator aims to give developers the ability to write more type-safe code in TypeScript. 

What is the TypeScript satisfies operator?

Have you ever wondered how to check if a type has all the required properties and methods of a specific interface in TypeScript? If so, you might be interested in learning about the satisfies operator in TypeScript and how to use it. In this article, I will explain what the satisfies operator is and how it works, and show you some examples of how to use the satisfies operator in your code.

The satisfies operator is a feature in TypeScript that allows you to check if a given type satisfies a specific interface or condition . In other words, it ensures that a type has all the required properties and methods of a specific interface . It is a way to ensure a variable fits into a definition of a type . For example, you can use the satisfies operator to check if a function parameter is an object that has a name property and a greet method. To learn more about types in TypeScript, visit our Types vs. interfaces in TypeScript guide.

Life before the satisfies operator

Let’s look at an example to understand why the TypeScript satisfies operator is intriguing and what problems it solves. Let’s create a type MyState that’s going to be a union of two properties, as shown below:

type MyState = StateName | StateCordinates;

Here, we are creating a type of MyState that will be StateName or StateCordinates. This means MyState is a union type that will be StateName or StateCordinates. Then, we’ll define our StateName as a string with three values. So, our StateName can be either Washington, Detroit, or New Jersey, as shown below:

type StateName = "Washington" | "Detriot" | "New Jersey";

The next step is to define our StateCordinates with the following code:

type StateCordinates = {
  x: number;
  y: number;
};

Our StateCordinate is an object that has two properties, x and y. The x and y properties are going to be numbers. Now, we have our type StateName (a string) and StateCordinates, (an object). Finally, let’s create our type User with the code below:

type User = {
  birthState: MyState;
  currentState: MyState;
};

Our type User has two properties, birthState and currentState. Both of these properties are of type MyState. This means that each of these properties can be a StateName or StateCoordinates. Next, we’re going to create a variable user using the code below:

const user:User = {
  birthState: "Washington",
  currentState: { x: 8, y: 7 },
};

In the code above, the user will have the birthState property set to the "Washington" string as one of the values for the StateName type. The CurrentState property is set to an object with property x with the value of 8 and property y of 7, corresponding to the StateCordinates.

So, this is a perfectly valid way to create and annotate the user variable. Imagine we want to access the birthState variable and convert it to uppercase, like so:

user.birthState.toUpperCase();

If we hover our mouse over toUpperCase(), TypeScript will throw in an error stating that "property toUpperCase() does not exist on type MyState and property to uppercase does not exist on type StateCordinates", as shown below:

An Example of the Problems Before the Satisfies Operator in TypeScript

This is because TypeScript is not sure of the value of MyState or whether it is a string or an object because we defined MyState as a union of a string and an object. Essentially, it can be any of them. In order to remove this error, we need to manually validate the property before we can use the string method, like so:

if (typeof user.birthState === "string") {
  user.birthState.toUpperCase();
}

Here, we are writing a condition to check if it is a string, and if it is, then we can use the string method. Because we tested it as a string, the TypeScript error should disappear. Having to always validate whether it is a string can be frustrating and cumbersome. This is where the satisfies operator comes in.

Introducing the satisfies operator

While life before the satisfies operator required you to always validate whether the property was a string or not, with the satisfies operator, you don’t have to do this. Instead of defining the user variable manually, we can delete it and replace it with the satisfies operator, like so:

const user = {
  birthState: "Washington",
  currentState: { x: 7, y: 8 },
} satisfies User;
  user.birthState.toUpperCase();

The full code now looks like this:

type MyState = StateName | StateCordinates;
type StateName = "Washington" | "Detriot" | "New Jersey";
type StateCordinates = {
  x: number;
  y: number;
};
type User = {
  birthState: MyState;
  currentState: MyState;
};
const user = {
  birthState: "Washington",
  currentState: { x: 8, y: 7 },
} satisfies User;
user.birthState.toUpperCase();

The satisfies operator prevalidates all the object properties for us, and the TypeScript error no longer pops up. Now, the satisfies operator will validate our user properties for us. Not only that, but it will also check in advance if any of the properties contain a string or an object.

Thanks to the satisfies operator; TypeScript knows that our birthState is a string and not an object because it has prevalidated/checked the values of all properties of the User. And, if we try adding something else to the birthState property that doesn’t correspond to any of the defined types, we’ll get an error.

The satisfies keyword ensures that we only pass whatever satisfies the User type to the user variable, allowing TypeScript to do its type inference magic. Let’s look at some other examples.

Property name constraining

We can also use the satisfies operator to tell the TypeScript compiler that it’s OK for an object to include only a subset of the given keys but not accept others. Here’s an example:

type Keys = 'FirstName' |"LastName"| "age"|"school"| "email"
const student = {
  FirstName: "Temitope",
  LastName: "Oyedele",
  age: 36,
  school:"oxford",
}satisfies Partial<Record<Keys, string | number>>;

student.FirstName.toLowerCase();
student.age.toFixed();

By using the satisfies operator in the code above, we instruct the TypeScript compiler that the type of the student object must match the PartialRecordKeys, string | number>> type. The Partial type in TypeScript is an inbuilt type that helps manipulate other user-defined types.

Property name fulfillment

Similar to property name constraining, with the exception that in addition to restricting objects to only contain specific properties, we can also ensure that we get all of the keys using the satisfies operator. Here’s what that looks like:

type Keys = 'FirstName' |"LastName"| "age"|"school"

const student = {
  FirstName: "Temitope",
  LastName: "Oyedele",
  age: 36,
  school:"oxford",
}satisfies Record<Keys, string | number>;

student.age.toFixed();
student.school.toLowerCase();

Here, we use the satisfies operator with the Record<Keys, string | number> to check that an object has all the keys specified by the Keys type and has a value of either string or number type associated with each key.

Property value conformance

The satisfies operator is not only capable of restricting the names of properties in an object, but it can also restrict the values of those properties. Suppose we have a library object with various books, each represented as an object with properties for the book’s title, author, and year of publication. However, we mistakenly used a string instead of a number for the year of publication of the book "Pride and Prejudice".

To catch this error, we can use the satisfies operator to ensure that all properties of the library object are of type Book, which we defined as an object with the required "title", "author", and "year" properties, where "year" is a number. Here’s what that looks like:

type Book = { title: string, author: string, year: number };

const library = {
  book1: { title: "Things fall apart", author: "Chinua Achebe", year: 1958 },
  book2: { title: "Lord of the flies", author: "William Golding", year: 1993 },
  book3: { title: "Harry Potter", author: "J.k Rowling", year: "1997" }, // Error
} satisfies Record<string, Book>;

With the help of the satisfies operator, the TypeScript compiler can find the error and prompt us to correct the year property of the book "Harry Potter".

The benefits of using the satisfies operator

The satisfies operator allows us to improve the quality and scalability of our code. However, the satisfies operator’s main benefits are type safety, code correctness, validation, code reusability, and code organization.

Type safety

The satisfies operator ensures that types have all the necessary methods and properties, ultimately reducing the likelihood of runtime problems. You may catch type problems at build time or before your code is run by using the satisfies operator.

Code correctness

We can use the satisfies operator to ensure the correctness of code because it allows us to check if a given type satisfies a particular condition.

Validation

The satisfies operator enables us to verify that an expression’s type matches another type without declaring a new variable or casting the expression to a different type.

Code reusability

Using the satisfies operator helps ensure that different parts of our application can consistently work with the same types of data. This helps to make code more modular and reusable.

Code organization

Using the TypeScript satisfies operator helps organize your code into logical blocks based on the type of a value. This can help eliminate repeated type checks in various sections of your code and make it easier to read and comprehend.

Conclusion

In summary, the TypeScript satisfies operator is a convenient and useful feature that can help improve the quality and scalability of your code. It does the heavy lifting by prevalidating your values, giving you a more flexible and precise type-checking experience. You can also use the satisfies operator to create more robust and maintainable code. If you are interested in learning more about how to use the satisfies operator in your TypeScript projects, you can follow this tutorial or check out some of the examples below. Happy coding!

No comments:

Post a Comment