Marius Schulz
Marius Schulz
Front End Engineer

Mapped Type Modifiers in TypeScript

With TypeScript 2.1, mapped types were added to the language in December 2016. As of TypeScript 2.8, mapped types have gained the ability to add or remove a particular modifier from a property. Previously, it was only possible to add modifiers to properties, but not remove them.

#The ? Property Modifier

You can make any property of an object type optional by adding a ? after the property name in the type declaration:

interface TodoItem {
  description: string;
  priority?: "high" | "medium" | "low";
}

With the ? modifier in place, the priority property can be specified when creating an object of the TodoItem type, but it doesn't have to be:

// We can set the `priority` property to one of the 3 values
const todo1: TodoItem = {
  description: "Mow the lawn",
  priority: "high",
};

// Or we can leave it out entirely (since it's optional)
const todo2: TodoItem = {
  description: "Mow the lawn",
};

// Or we can explicitly set the value `undefined`
const todo3: TodoItem = {
  description: "Mow the lawn",
  priority: undefined,
};

We've seen how to mark a specific property of a specific object type as optional. Let's now take a look at how we can define a generic type that applies the ? modifier to all properties of a given type.

#The Partial<T> Mapped Type

Transforming all properties of a given type is a perfect use case for mapped types. A mapped type lets us define a mapping function for types. That is, it can take all properties of an existing type, transform them using the mapping rule, and create a new type comprising the transformed properties.

Let's define a generic Partial<T> mapped type that adds the ? modifier to all properties of the type T:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

Our Partial<T> type uses the keyof operator to determine all property keys that T defines. It also uses the indexed access type T[P] to look up the type of each property P in T. Finally, it makes every property optional via the ? modifier.

If we apply Partial<T> to our TodoItem type from before, the resulting type will have two optional properties:

type PartialTodoItem = Partial<TodoItem>;
// {
//   description?: string | undefined;
//   priority?: "high" | "medium" | "low" | undefined;
// }

It turns out that the Partial<T> type is quite useful in many applications, which is why the TypeScript team has decided to include it in the lib.es5.d.ts file that ships as part of the typescript npm package:

/**
 * Make all properties in T optional
 */
type Partial<T> = {
  [P in keyof T]?: T[P];
};

#Removing the ? Mapped Type Modifier

We've seen how to use Partial<T> to add the ? modifier to all properties in a given type T. But how would you remove the ? modifier from all properties of a given type?

As of TypeScript 2.8, you can prefix the ? modifier with - to remove it from the property. A property that had its ? modifier removed then becomes a required property. The lib.es5.d.ts file now contains a new predefined Required<T> type that does exactly that:

/**
 * Make all properties in T required
 */
type Required<T> = {
  [P in keyof T]-?: T[P];
};

We can use Required<T> to make all properties of our TodoItem type required:

type RequiredTodoItem = Required<TodoItem>;
// {
//   description: string;
//   priority: "high" | "medium" | "low";
// }

Note that after this transformation, the priority property is no longer optional.

#Adding the ? Mapped Type Modifier

We've seen how to remove the ? modifier using -?. To preserve symmetry and consistency, TypeScript allows you to write +? to add the ? modifier to a property. You could define the Partial<T> type like this, if you wanted to:

type Partial<T> = {
  [P in keyof T]+?: T[P];
};

Note that a property modifier without a + or - prefix is equivalent to that same property modifier with a + prefix. There's no benefit to writing +? instead of ?. I would recommend you stick with ? as that's the syntax used when defining optional properties within an interface or a type alias.

#The readonly Property Modifier

The readonly modifier can be used in a mapped type to make the resulting properties read-only:

type ReadonlyTodoItem = Readonly<TodoItem>;
// {
//   readonly description?: string | undefined;
//   readonly priority?: "high" | "medium" | "low" | undefined;
// }

The compiler will issue an error if you try to assign a value to a read-only property:

const todo: ReadonlyTodoItem = {
  description: "Mow the lawn",
  priority: "high",
};

// Error: Cannot assign to 'priority'
// because it is a read-only property.
todo.priority = "medium";

#Removing the readonly Mapped Type Modifier

Similar to how you can remove the ? modifier from a property using -?, you can remove the readonly modifier from a property using -readonly. Let's define our own Mutable<T> mapped type that removes the readonly modifier from all properties defined by T:

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Now, the following piece of code type-checks correctly and the compiler no longer complains about an assignment to a read-only property:

const todo: Mutable<ReadonlyTodoItem> = {
  description: "Mow the lawn",
  priority: "high",
};

todo.priority = "medium";

#Adding the readonly Mapped Type Modifier

Similar to how you can write +? instead of ? to add the ? modifier to a property, you can write +readonly instead of readonly to add the readonly modifier. You could therefore rewrite the predefined Readonly<T> mapped type like this:

type Readonly<T> = {
  +readonly [P in keyof T]: T[P];
};

Again, I would recommend you stick with the plain readonly modifier as there's no benefit to writing +readonly instead.

This article and 44 others are part of the TypeScript Evolution series. Have a look!