Borislav Hadzhiev
Fri Feb 18 2022·3 min read
Photo by Ashim D’Silva
Use the Record
utility type to enforce the type of an object's values in
TypeScript, e.g. type Animal = Record<string, string>
. The Record
utility
type constructs an object type, whose keys and values are of specific type.
// 👇️ function returning an object // whose keys and values are of type string function getObj(): Record<string, string> { return { name: 'Tom', country: 'Chile' }; } type Animal = Record<string, string>; const a1: Animal = { name: 'Alfred', type: 'dog' };
The Record utility type can be used to map the properties of a type to another type.
The first type we passed to the generic is the type for the Keys
, and the
second - the type for the values.
type Animal = Record<string, number>; const a1: Animal = { age: 3, num: 6 };
If you know the names of the keys ahead of time, you can limit them by using a union.
type Animal = Record<'name' | 'type' | 'age', string | number>; const a1: Animal = { name: 'Alfred', type: 'dog', age: 3 };
In the example we've typed the object to only have keys name
, type
and age
that are of type string or number.
The type checker would throw an error if we try to set a key with different name on the object.
type Animal = Record<'name' | 'type' | 'age', string | number>; // ⛔️ Error: Type '{ country: string; type: string; age: number; }' // is not assignable to type 'Animal'. const a1: Animal = { country: 'UK', type: 'dog', age: 3 };
Here is an example of limiting an object's keys to a union of specific strings
and setting its values to be objects containing the role
and salary
properties.
interface EmployeeData { role: string; salary: number; } type EmployeeName = 'tom' | 'alice' | 'jeff'; const employees: Record<EmployeeName, EmployeeData> = { tom: { role: 'accountant', salary: 10 }, alice: { role: 'manager', salary: 15 }, jeff: { role: 'programmer', salary: 17 }, };
Trying to add a key that does not exist on the object would cause the type checker to throw an error.
interface EmployeeData { role: string; salary: number; } type EmployeeName = 'tom' | 'alice' | 'jeff'; const employees: Record<EmployeeName, EmployeeData> = { tom: { role: 'accountant', salary: 10 }, alice: { role: 'manager', salary: 15 }, jeff: { role: 'programmer', salary: 17 }, // ⛔️ Error: Object literal may only specify known // properties, and 'ben' does not exist in type // 'Record<EmployeeName, EmployeeData>'. ben: { role: 'programmer', salary: 17 }, };
The Record
utility type allows us to be as broad or narrow as necessary when
typing an object's keys and values.
If you know the names of some of the object's keys, but want to be able to include keys with other names as long as they have values of the correct type, use a union.
interface EmployeeData { role: string; salary: number; } type EmployeeName = 'tom' | 'alice' | 'jeff'; // 👇️ use Union here const employees: Record<string | EmployeeName, EmployeeData> = { tom: { role: 'accountant', salary: 10 }, alice: { role: 'manager', salary: 15 }, jeff: { role: 'programmer', salary: 17 }, ben: { role: 'programmer', salary: 17 }, };
Even though ben
is not in the EmployeeName
type, we are able to add the
property to the object, because it has a value that is of type EmployeeData
.
This allows us to be specific about the names of the keys we know about, but still allow for other keys, as long as they have a value of the correct type.