Borislav Hadzhiev
Thu Feb 24 2022·3 min read
Photo by Robson Morgan
Use an index signature to dynamically add properties to an object, e.g.
const obj: {[key: string]: any} = {}
. Index signatures are used when we don't
know all of the names of a type's properties and the type of their values ahead
of time.
interface Person { [key: string]: any; } const obj: Person = {}; obj.name = 'Tom'; obj.age = 30;
The {[key: string]: any}
syntax is an
index signature
in TypeScript and is used when we don't know all the names of a type's
properties and the shape of the values ahead of time.
string
, it will return a value of any
type.You might also see the index signature {[key: string]: string}
in examples. It
represents a key-value structure that when indexed with a string
returns a
value of type string
.
If the value of the string keys were set to any
in the index signature, you
could add properties to the object of any type, since anything is more specific
than any
.
interface Person { [key: string]: any; age: number; name: string; country?: string; } const obj: Person = { name: 'Tom', age: 30, }; obj.country = 'Chile'; obj.language = 'TypeScript';
This is a good way to narrow down the type of some of the properties that you know ahead of time.
For example, if I try to set the age
property to a string
, the type checker
would throw an error because it expects a number
.
interface Person { [key: string]: any; age: number; name: string; country?: string; } const obj: Person = { name: 'Tom', age: 30, }; // ⛔️ Error: Type 'string' is not // assignable to type 'number'.ts(2322) obj.age = '100';
We've already set the age
property to have a type of number
, so the type
checker helps us spot an error in our application.
A newer way to achieve this is to use the Record utility type.
Use the Record
utility type to dynamically add properties to an object, e.g.
const obj: Record<string, any>
. The Record
utility type constructs an object
type, whose keys and values are of specific type.
const obj: Record<string, any> = { name: 'Tom', }; obj.age = 30; obj.country = 'Chile';
We typed the object above to have keys of type string
and values of type
any
.
Keys
, and the second - the type for the values.If you know the type of some of the values ahead of time, you can specify them in an interface for better type safety.
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 }, }; // ⛔️ Type 'number' is not // assignable to type 'string'.ts(2322) employees.tom.role = 100; // ✅ still works employees.tom.hello = 'world';
The interface EmployeeData
extends from the Record
constructed type with
string keys and any
type values.
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.