Learn TypeScript

Using mapped type modifiers

Using mapped type modifiers

In this lesson, we will learn what a mapped type modifier is and how it is useful.

Understanding a mapped type modifier

We used a mapped type modifier in the last lesson. This was when we made the keys in the mapped type optional by using a question mark (?) in front of the key's type annotation:

{
[K in keyof T]?: TypeName
}

The question mark is called a modifier. Another modifier is readonly:

{
readonly [K in keyof T]: TypeName
}

The readonly modifier makes a property in the mapped type readonly. We will learn more about readonly properties later in this course.

What if we have a type with optional keys and want to map this to a type that has required keys? What if we have a type with readonly properties and want to map it to a type with writable properties? Well, we can use the - symbol before the modifier to denote that the modifier should be removed if it exists.

Using the - symbol before a ? will map to a required key:

{
[K in keyof T]-?: TypeName
}

Using the - symbol before the readonly keyword will map to a writable property:

{
-readonly [K in keyof T]: TypeName
}

An example

As an example, in the code editor below, we will create a mapped type to make all the keys required.

TypeScriptOpen exercise in CodeSandbox

The code contains a Contact type with a variable that uses this type. Notice that some of the properties within the Contact type are optional. We are going to create a mapped type that will turn these into required properties.

  • Let's begin to implement the mapped type by adding the following generic type alias:
type RequiredProperties<T> = {};
  • Add the mapping of the keys to our mapped type:
type RequiredProperties<T> = {
[K in keyof T]-?: string;
};

We have used the - symbol before the ? symbol to turn the keys into being required.

  • Update the bob variable to use our mapped type:
const bob: RequiredProperties<Contact> = {
name: "Bob",
};
🤔

A type error occurs on bob. Why is this so?.

  • Add an email property to bob:
const bob: RequiredProperties<Contact> = {
name: "Bob",
email: "bob@somewhere.com",
};

The type error is resolved.

Great!

  • Let's add another property to the Contact type:
type Contact = {
...
age?: number;
}
  • Let's add a value for age to bob:
const bob: RequiredProperties<Contact> = {
name: "Bob",
email: "bob@somewhere.com",
age: 30,
};
🤔

A type error occurs on age in bob. Why is this so?.

  • We can resolve the type error by updating RequiredProperties as follows:
type RequiredProperties<T> = {
[K in keyof T]-?: T[K];
};

We have changed the key type from string to T[K]. This gets the corresponding type from the type being mapped from. It is called a lookup type or sometimes an indexed access type.

The type errors are now resolved.

🤔

What is the type of the age property in bob now?.

Neat!

We have actually just created a type that already exists as a standard utility type called Required. The definition of Required is below:

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

This is the same as the mapped type we created apart from its name.

Summary

Mapped type modifiers add flexibility in mapped types to make properties required and writable.

Did you find this lesson useful?

Share this lesson on twitter
© 2023 Carl Rippon
Privacy Policy
This site uses cookies. Click here to find out more