Mapped Types
In generally programming, a map is a data structure that allows us to store key-value pairs. In type programming, we can think of a mapped type as a map. We can declare a mapped type that maps a set of keys to a set of values.
Declare a Mapped Type
For example, we can declare a mapped type CopyMap
that just copies the input type T
. This is similar to declaring a map that copies the key-value pairs of another map. The result of the mapped type CopyMap
is the same as the input type T
.
// Declare a mapped type
type Person = {
name: string;
age: number;
};
type CopyMap<T> = {
[Key in keyof T]: T[Key];
};
// Test the mapped type
type Test = CopyMap<Person>;
// End of the example
In the example above, CopyMap
Type return the same thing that input from T
, we declare a mapped type CopyMap
that maps a set of keys Key
to a set of values T[Key]
. We use the keyof
operator to get the keys of the type T
and the in
operator to iterate over the keys. We then use the key Key
to access the value T[Key]
and map it to the key Key
.
Thinking Mapped Types are Loops
Too hard to understand? You can think of mapped types as loops. We loop in each key of Person
and map it to the value of Person
type. Check the js-like pseudo code below:
function CopyMap(Person) {
const Result = {};
for(const Key in Object.keys(Person)) {
Result[Key] = Person[Key];
}
return Result;
}
But wait!, we can't use for
loop in type programming. We use mapped types to loop over the keys of a type and map them to the values of the type. This is similar to using a loop to iterate over the keys of an object and map them to the values of the object.
Go back to CopyMap
type, in the code below:
type CopyMap<T> = {
[Key in keyof T]: T[Key];
};
We use the keyof
operator which returns the keys of the type Person
, that is "name" | "age"
in this case. We then use the in
operator to iterate over the keys and map them to the values of the type Person
. The result of the mapped type CopyMap
is the same as the input type Person
.
Note that, when we use in
operator, we can act of union types to be arrary of keys (From the programming realm, we can't do this). For example, keyof Person
returns "name" | "age"
, we can think of it as ["name", "age"]
in the programming realm.
Examples
1. Loop for Add Prefix to Keys
type AppendPrefix<T, U extends string> = {
[K in keyof T as `${U}${K & string}`]: T[K]
};
function appendPrefix<T extends Record<string, any>, U extends string>(obj: T, prefix: U) {
const result: Partial<AppendPrefix<T, U>> = {};
for (const [key, value] of Object.entries(obj)) {
result[`${prefix}${key}`] = value;
}
return result;
}
interface Person {
name: string;
age: number;
location: string;
}
const person: Person = {
name: 'John',
age: 30,
location: 'Thailand'
}
// Example
const oldPerson = appendPrefix(person, 'old_');
oldPerson.old_age;
oldPerson.old_location;
oldPerson.old_name;