Define a Type for Object with Dynamic keys in TypeScript

avatar

Borislav Hadzhiev

Last updated: Jul 25, 2022

banner

Photo from Unsplash

Define a Type for Object with Dynamic keys in TypeScript #

Use an index signature to define a type for an object with dynamic keys, e.g. [key: string]: string;. Index signatures are used when we don't know all of the names of a type's properties ahead of time, but know the shape of the values.

index.ts
interface Person { // 👇️ key value [key: string]: string | number; } const obj: Person = { name: 'Tom', }; obj.age = 30; obj.country = 'Chile';

The {[key: string]: string | number} syntax is an index signature in TypeScript and is used when we don't know all the names of a type's properties ahead of time, but know the shape of the values.

The index signature in the example means that when the object is indexed with astring, it will return a value of typestring or number.

If we try to set a property with a different value on the object, we'd get an error.

index.ts
interface Person { // 👇️ key value [key: string]: string | number; } const obj: Person = { name: 'Tom', }; // ⛔️ Type 'boolean' is not assignable // to type 'string | number'.ts(2322) obj.isProgrammer = true;

If you know any of the names of the object's keys, make sure to add them to the interface.

index.ts
interface Person { // 👇️ key value [key: string]: string | number; name: string; // 👈️ add `name` key } const obj: Person = { name: 'Tom', };

We added the name key, because we know it will exist in the object in advance.

Note that the type of the name key has to either be string or number, because we've already specified that when the object is indexed with ANY string, it returns a value that is a string or a number.

We basically have to set our index signature to the most generic type in the object and narrow down the type of the keys we know in advance.

Likely, the type of the values in your object will be different, so you will have to adjust this.

If you don't know the type of the values in advance, you can set the values to be of type any.

index.ts
interface Person { // 👇️ key value [key: string]: any; } const obj: Person = { name: 'Tom', }; obj.age = 30; obj.country = 'Chile';

Now we have an object with dynamic keys and values. This type is very broad, but it has its use cases.

A newer way to achieve this is to use the Record utility type.

Use the Record utility type to define a type for an object with dynamic keys, e.g. const obj: Record<string, string>. The Record utility type constructs an object type, whose keys and values are of a specific type.

index.ts
// 👇️ keys 👇️ values const obj: Record<string, string | number> = { name: 'Tom', }; obj.age = 30; obj.country = 'Chile';

We typed the object to have keys of type string and values of type string or number.

The first type we passed to the generic is the type for the Keys, and the second is the type for the values.

If you don't know the type of all of the values ahead of time, create an interface that extends from the Record constructed type.

index.ts
interface EmployeeData extends Record<string, any> { role: string; salary: number; color?: string; } const employees: Record<string, EmployeeData> = { tom: { role: 'accountant', salary: 10, color: 'blue' }, alice: { role: 'manager', salary: 15 }, jeff: { role: 'programmer', salary: 17 }, ben: { role: 'programmer', salary: 17 }, }; // ⛔️ Error: Type '5' is not assignable to // type 'string | undefined'.ts(2322) employees.tom.color = 5; // ✅ still works employees.tom.hello = 'world';

The interface EmployeeData extends from the Record constructed type with string keys and any type values.

The interface sets a specific type for the 3 properties that we know about in advance.

If we try to set the role, salary or color properties to an incompatible type, we'd get an error.

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.