TypeScript - Mapped Types



Mapped types in TypeScript are used to create new types by transforming the properties of existing types. Mapping means creating new elements from the existing ones after making some changes. Similarly, mapped type means creating new types after making changes to the existing types. So, you can reuse the existing types to create new types.

Built-in Mapped Types

The built-in mapped type allows you to transform the existing types without manually transforming types.

Here are the built-in mapped types available in TypeScript.

  • Partial<T>: It creates a new type by making all properties optional of the existing type.

  • Required<T>: It creates a new type by making all properties required of the existing type.

  • Readonly<T>: Makes all properties optional in a new type.

  • Record<K, T>: Creates a type with a set of properties K of type T. Useful for mapping properties of the same type.

  • Pick<T, K>: Creates a new type by picking a set of properties K from T.

  • Omit<T, K>: Creates a new type by omitting a set of properties K from T.

  • Exclude<T, U>: It creates a new type by excluding all properties from T that are assignable to U.

  • Extract<T, U>: It creates a new type by extracting all properties from T that are assignable to U.

  • NonNullable<T>: Constructs a type by excluding null and undefined from T.

Examples

Let's understand some of the commonly used built-in mapped types with help of some program examples in TypeScript.

Example: Using the Partial Type

In the code below, we have created the Person type containing the name and age properties. After that, we used the Partial utility type to create a new type from the Person type.

The PartialPerson type has all the properties of the Person class but all are optional. We have defined the partialPerson object of the PartialPerson type, which contains only the name property as all properties are optional.

// Creating the Person type
type Person = {
    name: string;
    age: number;
};

// Using the Partial mapped type
type PartialPerson = Partial<Person>;

// Creating an object of type PartialPerson
const partialPerson: PartialPerson = {
    name: "John",
    //   Age is optional
};
// Output
console.log(partialPerson);

On compiling, it will generate the following JavaScript code.

// Creating an object of type PartialPerson
var partialPerson = {
    name: "John"
};
// Output
console.log(partialPerson);

Output

{ name: 'John' }

Example: Using the ReadOnly Type

In the code below, we used the Readonly utility type to create a new type having all properties readonly from the Person type. If we try to change any property of the partialPerson object, it will throw an error as it is readonly.

// Creating the Person type
type Person = {
    name: string;
    age: number;
};

// Using the ReadOnly mapped type
type PartialPerson = Readonly<Person>;

// Creating an object of type PartialPerson
const partialPerson: PartialPerson = {
    name: "John",
    age: 30
};

// Trying to change the name property
// partialPerson.name = "Doe"; // Error: Cannot assign to 'name' because it is a read-only property.
// Output
console.log(partialPerson);

On compiling, it will generate the following JavaScript code.

// Creating an object of type PartialPerson
const partialPerson = {
    name: "John",
    age: 30
};
// Trying to change the name property
// partialPerson.name = "Doe"; // Error: Cannot assign to 'name' because it is a read-only property.
// Output
console.log(partialPerson);

Output

{ name: 'John', age: 30 }

Example: Using the Pick Type

In the code below, we have created the Animal type which contains 3 properties.

After that, we used the Pick utility type to pick only name and species properties from the Animal type and create a new type named 'AnimalNameAndSpecies'.

The animalNameAndSpecies object is of type AnimalNameAndSpecies, which contains only name and species properties.

// Creating the Animal type
type Animal = {
    name: string;
    species: string;
    age: number;
};
// Using the Pick utility type to create a new type
type AnimalNameAndSpecies = Pick<Animal, 'name' | 'species'>;
// Creating an object of the AnimalNameAndSpecies type
const animalNameAndSpecies: AnimalNameAndSpecies = {
    name: 'Milo',
    species: 'Dog'
};
console.log(animalNameAndSpecies);

On compiling, it will generate the following JavaScript code.

// Creating an object of the AnimalNameAndSpecies type
const animalNameAndSpecies = {
    name: 'Milo',
    species: 'Dog'
};
console.log(animalNameAndSpecies);

Output

{ name: 'Milo', species: 'Dog' }

Creating Custom Mapped Types

Utility functions have limited functionalities. So, it is important to create the custom mapped types to map types according to your own conventions.

Syntax

You can follow the syntax below to create a custom-mapped type.

type MyMappedType<T> = {
    [P in keyof T]: NewType;
};
  • 'T' is an existing type from which we need to create a new custom-mapped type.

  • 'P' is a property of the type.

  • The 'keyof' operator gets each key of the type 'T'.

  • NewType is a new type to assign to the type property 'P'.

Example: Making all Properties Boolean

In the code below, we created the Booleanify type which takes the type as a generic parameter. Inside the type body, we traverse each key of the type 'T' using the '[P in keyof T]' code, and assign a boolean to it as we are changing the type of all properties to a boolean.

The boolPerson object has all properties of the boolean type.

// Defining the Person type
type Person = {
  name: string;
  age: number;
};

// Creating the custom mapped type
type Booleanify<T> = {
    // Making all properties of Person type to Boolean
  [P in keyof T]: boolean;
};

// Creating an object of Booleanify type
const boolPerson: Booleanify<Person> = {
  name: true,
  age: true,
};
console.log(boolPerson);

On compiling, it will generate the following JavaScript code:

// Creating an object of Booleanify type
const boolPerson = {
    name: true,
    age: true,
};
console.log(boolPerson);

Output

The above example code will produce the following output:

{ name: true, age: true }

Example: Making all Properties Optional

In this example, we are making all properties of existing types optional without using the built-in utility type Partial.

Here, the Animal class contains the name and legs properties. In the code [key in keyof Animal]?: Animal[key], question mark (?) makes properties optional, and Animal[key] helps in keeping the property type same.

Now, In the customAnimal object, all properties are optional.

// Defining the Animal type
type Animal = {
    name: string;
    legs: number;
};

// Creating custom type and making all properties optional using mapping
type CustomAnimal = {
    [key in keyof Animal]?: Animal[key];
};

// Creating an object of type CustomAnimal
let customAnimal: CustomAnimal = {
    name: "Dog",
    legs: 4,
};

console.log(customAnimal);

On compiling, it will generate the following JavaScript code:

// Creating an object of type CustomAnimal
let customAnimal = {
    name: "Dog",
    legs: 4,
};
console.log(customAnimal);

Output

The output of the above example code is as follows:

{ name: 'Dog', legs: 4 }

You can either use the utility functions or create custom mapped types to reuse the existing types to create new types. These custom mapped types improve the readability of the code and help developers to make code maintainable.

Advertisements