Index Signatures
Sometimes you don’t know all the names of a type’s properties ahead of time, but you do know the shape of the values. In those cases you can use an index signature to describe the types of possible values, for example:
interface StringArray {
[index: number]: string;
}
const myArray: StringArray = getStringArray();
const secondItem = myArray[1]; // secondItem is a string
Above, we have a StringArray
interface which has an index signature. This index signature states that when a StringArray
is indexed with a number
, it will return a string
.
Only some types are allowed for index signature properties: string
, number
, symbol
, template string patterns, and union types consisting only of these.
Properties
While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type:
interface NumberDictionary {
[index: string]: number;
length: number; // ok
name: string; // Error: Property 'name' of type 'string' is not assignable to 'string' index type 'number'
}
However, properties of different types are acceptable if the index signature is a union of the property types:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
Finally, you can make index signatures readonly in order to prevent assignment to their indices:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
When to use Index Signatures
Imagine you have 2 objects describing the salary of two employees:
const salary1 = {
salary: 55_000,
bonus: 5_000
};
const salary2 = {
contractSalary: 75_000
};
You want to implement a function that returns the total remuneration based on the salary object:
function totalSalary(salaryObject: ???) {
let total = 0;
for (const name in salaryObject) {
total += salaryObject[name];
}
return total;
}
totalSalary(salary1); // => 60 000
totalSalary(salary2); // => 75 000
In this case an index signature fits perfectly:
function totalSalary(salaryObject: { [key: string]: number }) {
let total = 0;
for (const name in salaryObject) {
total += salaryObject[name];
}
return total;
}
totalSalary(salary1); // => 60 000
totalSalary(salary2); // => 75 000