Index Signatures
An Object
in JavaScript (and hence TypeScript) can be accessed with a string to hold a reference to any other JavaScript object.
Here is a quick example:
We store a string "World"
under the key "Hello"
. Remember we said it can store any JavaScript object, so lets store a class instance just to show the concept:
Also remember that we said that it can be accessed with a string. If you pass any other object to the index signature the JavaScript runtime actually calls .toString
on it before getting the result. This is demonstrated below:
Note that toString
will get called whenever the obj
is used in an index position.
Arrays are slightly different. For number
indexing JavaScript VMs will try to optimise (depending on things like is it actually an array and do the structures of items stored match etc.). So number
should be considered as a valid object accessor in its own right (distinct from string
). Here is a simple array example:
So that's JavaScript. Now let's look at TypeScript's graceful handling of this concept.
TypeScript Index Signature
First off, because JavaScript implicitly calls toString
on any object index signature, TypeScript will give you an error to prevent beginners from shooting themselves in the foot (I see users shooting themselves in the foot when using JavaScript all the time on stackoverflow):
The reason for forcing the user to be explicit is because the default toString
implementation on an object is pretty awful, e.g. on v8 it always returns [object Object]
:
Of course number
is supported because
its needed for excellent Array / Tuple support.
even if you use it for an
obj
its defaulttoString
implementation is nice (not[object Object]
).
Point 2 is shown below:
So lesson 1:
TypeScript index signatures must be either
string
ornumber
Quick note: symbols
are also valid and supported by TypeScript. But let's not go there just yet. Baby steps.
Declaring an index signature
So we've been using any
to tell TypeScript to let us do whatever we want. We can actually specify an index signature explicitly. E.g. say you want to make sure that anything that is stored in an object using a string conforms to the structure {message: string}
. This can be done with the declaration { [index:string] : {message: string} }
. This is demonstrated below:
TIP: the name of the index signature e.g.
index
in{ [index:string] : {message: string} }
has no significance for TypeScript and is only for readability. e.g. if it's user names you can do{ [username:string] : {message: string} }
to help the next dev who looks at the code (which just might happen to be you).
Of course number
indexes are also supported e.g. { [count: number] : SomeOtherTypeYouWantToStoreEgRebate }
All members must conform to the string
index signature
string
index signatureAs soon as you have a string
index signature, all explicit members must also conform to that index signature. This is shown below:
This is to provide safety so that any string access gives the same result:
Using a limited set of string literals
An index signature can require that index strings be members of a union of literal strings by using Mapped Types e.g.:
This is often used together with keyof typeof
to capture vocabulary types, described on the next page.
The specification of the vocabulary can be deferred generically:
Having both string
and number
indexers
string
and number
indexersThis is not a common use case, but TypeScript compiler supports it nonetheless.
However, it has the restriction that the string
indexer is more strict than the number
indexer. This is intentional e.g. to allow typing stuff like:
Design Pattern: Nested index signature
API consideration when adding index signatures
Quite commonly in the JS community you will see APIs that abuse string indexers. e.g. a common pattern among CSS in JS libraries:
Try not to mix string indexers with valid values this way. E.g. a typo in the padding will remain uncaught:
Instead separate out the nesting into its own property e.g. in a name like nest
(or children
or subnodes
etc.):
Excluding certain properties from the index signature
Sometimes you need to combine properties into the index signature. This is not advised, and you should use the Nested index signature pattern mentioned above.
However, if you are modeling existing JavaScript you can get around it with an intersection type. The following shows an example of the error you will encounter without using an intersection:
Here is the workaround using an intersection type:
Note that even though you can declare it to model existing JavaScript, you cannot create such an object using TypeScript:
Last updated