Simple Guide to TypeScript’s Satisfies Operator

Share with a friend:

With each new release, TypeScript introduces innovative features that enhance developer productivity and code quality. One such feature that has caught the attention of the TypeScript community is the Satisfies Operator, a versatile addition that brings a new dimension to type validation and inference.

Introduction to the Satisfies Operator

TypeScript, being a statically typed superset of JavaScript, allows developers to catch type-related errors at compile time rather than runtime. The Satisfies Operator, released in TypeScript v4.9, is a new addition to TypeScript’s arsenal that enables developers to validate whether an expression matches a specific type without altering the inferred type of the expression.

Consider the following scenario: you have an object containing various properties, and you want to ensure that each property adheres to a certain type. Traditionally, you might annotate the object with specific types, but this approach sacrifices valuable type inference. Here’s where the Satisfies Operator comes into play.

Scenario: Ensuring Type Compatibility

Suppose you’re working on a project that involves managing user profiles, where each user has a unique ID and can have multiple contact methods associated with them. Each contact method can either be an email address (string) or a phone number (number).

type UserID = "user1" | "user2" | "user3";
const userContacts: Record<UserID, string | number[]> = {
    user1: "[email protected]",
    user2: [1234567890, 9876543210],
    user3: "[email protected]",
};

Ideally, we would want to use array methods on user2 (at, pop, push, etc.). While for user1 and user3, we would want to use any of Javascript’s string methods (toUpperCase, trim, etc.). However, we will quickly run into problems as the type of string | number[] is not specific enough.


One way of removing the error is by manually validating the property:

if (typeof userContacts.user1 == "string") {
    userContacts.user1.toUpperCase();
}

However, with the new Satisfies Operator, the solution is much more simple:

type UserID = "user1" | "user2" | "user3";


const userContacts = {
    user1: "[email protected]",
    user2: [1234567890, 9876543210],
    user3: "[email protected]",
} satisfies Record<UserID, string | number[]>;

userContacts.user1.toUpperCase();
userContacts.user2.at(1);

// No errors!

In this example, the Satisfies Operator ensures that the properties of userContacts conform to the specified types, while still allowing you to use array or string methods on the properties as needed. There is also a key difference between the satisfies operator and using the colon annotation. When you use the satisfies operator the value always beats the type.

Using Satisfies to Catch Typos

The power of the Satisfies Operator becomes even more evident when dealing with potential typos in your code. In this example, notice the intentional typo "usee3" instead of "user3". The Satisfies Operator immediately detects this typo:

type UserID = "user1" | "user2" | "user3";

const userContacts = {
    user1: "[email protected]",
    user2: [1234567890, 9876543210],
    usee3: "[email protected]",
} satisfies Record<UserID, string | number[]>;

/*
  Type '{ user1: string; user2: number[]; usee3: string; }' does not satisfy the expected type 'Record<UserID, string | number[]>'.
  Object literal may only specify known properties, but 'usee3' does not exist in type 'Record<UserID, string | number[]>'. Did you mean to write 'user3'?
*/

This ability to catch typos while preserving type inference is a game-changer for maintaining code quality.

Validating Object Property Keys

The Satisfies Operator isn’t just limited to type validation; it also helps ensure the correctness of object property keys. Let’s say you have a set of allowed colors represented as a type and you want to ensure that an object only contains properties with these valid colors:

type ValidColors = "red" | "green" | "blue";

const favoriteColors = {
  red: "no",
  green: false,
  blue: "#0000FF",
  car: false // Error: 'car' does not exist in type 'Record<ValidColors, unknown>'
} satisfies Record<ValidColors, unknown>;

By using the Satisfies Operator, you can enforce that the object only contains keys that are defined in the ValidColors type, preventing accidental typos or extraneous properties.

Type Conformance for Object Property Values

The Satisfies Operator’s versatility extends to enforcing type conformance for object property values. You might want to ensure that all properties of an object adhere to a specific type, while allowing flexibility in property names:

type Employee = {id: number; name: string; salary: number};

const bakeryStore = {
    emp1: {id: 1, name: "John Brown", salary: 20000},
    emp2: {id: 2, name: "Sue Wilkins", salary: 15000},
    emp3: {id: 1, name: "Adonis Williams", salary: "30000"}, // Error: Type 'string' is not assignable to type 'number'.
} satisfies Record<string, Employee>;

In this case, the Satisfies Operator checks that each property’s value conforms to the specified type while keeping the flexibility of property names.

Practical Examples

Let’s dive into a few practical examples showcasing the power of the Satisfies Operator.

Example 1: Restricting Property Names

type Fruit = "apple" | "banana" | "orange";

const fruitPrices = {
  apple: 0.5,
  banana: 0.25,
  orange: 0.75,
  pear: 1.0 // Error: "pear" is not a valid fruit
} satisfies Record<Fruit, number>;

In this example, the Satisfies Operator ensures that the fruitPrices object only contains properties corresponding to valid fruit names.

Example 2: Ensuring Numeric Values

type Measurements = "height" | "width" | "depth";

const boxDimensions = {
  height: 10,
  width: "20", // Error: should be a number
  depth: 15
} satisfies Record<Measurements, number>;

By using the Satisfies Operator, you can enforce that all measurements are represented as numeric values, catching potential errors early.

Example 3: Conforming to Specific Format

type Currency = "USD" | "EUR" | "JPY";

const currencySymbols = {
  USD: "$",
  EUR: "",
  JPY: "¥",
  GBP: "£" // Error: "GBP" is not a valid currency
} satisfies Record<Currency, string>;

In this example, the Satisfies Operator ensures that the currencySymbols object adheres to the specified format for each currency.

Conclusion

The TypeScript 4.9 release introduces the powerful Satisfies Operator, which enhances type validation without sacrificing type inference. This operator opens up new possibilities for catching errors early, enforcing correct type usage, and maintaining code quality.

Share with a friend:

Rajae Robinson

Rajae Robinson is a young Software Developer with over 3 years of work experience building websites and mobile apps. He has extensive experience with React.js and Next.js.

More Typescript Posts