Enforce the Type of an Object's Values in TypeScript

avatar

Borislav Hadzhiev

Fri Feb 18 20223 min read

Enforce the Type of an Object's Values in TypeScript #

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.

index.ts
// 👇️ 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.

index.ts
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.

index.ts
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.

index.ts
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.

index.ts
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.

index.ts
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.

index.ts
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.

Use the search field on my Home Page to filter through my more than 1,000 articles.