Skip to main content

Record<Keys, Type> Utility Types in TypeScript

The Record type is a type that can be used to combine two types.

The Record type is a utility type that can be used to combine two types. It allows you to create an object type whose property keys are of a specific type and whose property values are of another specific type.

Syntax

Let's have a look at the Syntax of record type.

Record<Keys, Type>;
  • Keys: This is the type of object's keys that the record type generates for us.
  • Type: This is the type of the object's value generated by the record type.

Example

Let's take an example to understand how the Record type works. Let us suppose we are creating a blogging application and we have type roles which is a union type of three types of roles for the users of the application, the author, editor and a researcher.

type Roles = "author" | "editor" | "researcher";

Each of the users in the application would also need to have a name an email and age properties linked to their account. So we can create an interface for a User object as well.

interface User {
name: string;
email: string;
age: number;
}

Now moving on to articles in our blog, we would want each article to be an object which might have the following properties.

const article = {
title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
content: "Duis est urna, eleifend at malesuada id, suscipit eu",
// Contributors can be type generated from Roles type and User interface
contributors: {
author: { name: "John", email: "john@email.com", age: 32 },
editor: { name: "Frank", email: "frank@email.com", age: 36 },
researcher: { name: "Mark", email: "mark@email.com", age: 36 },
},
};

So our aim here is to type the article object strictly. If we look closely, contributors can be type generated from Roles type and User interface. If we look at the contributors object we can infer that each property key of the contributors object is each value from the Roles type, which is a union type and value for each of the properties of the contributors that is each of the users object complies with our User interface. So we can say that while the Roles type is the key and the User interface is the value of the contributors object. This is a perfect case for using the Record<Keys, Type> utility type. So let's create another interface called Article and start strictly typing the article object.

interface Article {
title: string;
content: string;
contributors: Record<Roles, User>;
}

The contributors property is of type Record<Roles, User>, which means that it is an object whose keys are of type Roles and whose values are of type User. The Record<Keys, Type> utility type will assign the exact shape to our contributors object as declared inside our article object. Now we can assign the Article interface to our article object and this is how the whole code would look like.

interface User {
name: string;
email: string;
age: number;
}

type Roles = "author" | "editor" | "researcher";

interface Article {
title: string;
content: string;
contributors: Record<Roles, User>;
}

const article: Article = {
title: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
content: "Duis est urna, eleifend at malesuada id, suscipit eu",
contributors: {
author: { name: "John", email: "john@email.com", age: 32 },
editor: { name: "Frank", email: "frank@email.com", age: 36 },
researcher: { name: "Mark", email: "mark@email.com", age: 36 },
},
};
TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

We used a union type to define Roles , but we could have used an enum as well to produce the same results. Here is an example, we can comment out the Roles type and declare and enum called Roles to produce the same results.

// type Roles  = "author" | "editor" | 'researcher';
enum Roles {
author = "author",
editor = "editor",
researcher = "researcher",
}

Using Mapped Types With Record Utility Type

Another scenario you'll often come across is where you would want to use mapped types along with the Record utility type. Here is an example, let us assume that in our application we have a frontend where the user has an interface to change the title and the content of the article and we need to extract those as strings before we parse them and send them to the API to be saved as new values.

In such scenario we can create a new type called ArticleData using the Record type and mapped types. This is how we can do it.

type ArticleData = Record<Article, string>;

If we were to infer the ArticleData type we would see that even the contributed is a part of it and this is how it looks like.

type ArticleData = {
title: string;
content: string;
contributors: string;
};

We might now want the contributors to be a part of the new type as we just need the data that is the title as well as the content. In this case, we can use another TypeScript utility type called the Omit<Type, Keys> type. The Omit<Type,Keys> utility type let's us omit or remove any of the properties if we want to from an object type or an interface and in this case it will be the contributors object. So we can further refine our ArticleData type like so:

type ArticleData = Record<keyof Omit<Article, "contributors">, string>;

Now we have ArticleData type that only contains the properties that we need.

type ArticleData = {
title: string;
content: string;
};

Now this might seem useless at this point and creating another type might seem to be a more convenient way of dealing with the problem, but when the interfaces that you need to inherit from become very long Record<Keys, Types> proves to be very useful.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for software developers.

YouTube @cloudaffle