Indexed Access Types in Typescript
In this post, we learn how we can look-up types inside types of objects and arrays.
Last week, we looked into the keyof
and typeof
operators and briefly touched on indexed access types in Typescript near the end of the post, which is today’s topic, and we will answer what they are and why we need them.
To understand index access types, we will start with an example. Take the following simple type for a person:
type Person = {
name: string;
age: number;
address: {
country: string;
province: string;
city: string;
street: string;
postalCode: string;
}
}
Now, let’s say we want to create a variable that’s typed as the address of the type Person
above. The first option you might think of is refactoring the above code so that address becomes a type of its own and is referenced within the Person
type in the address property, as shown below:
type Address = {
country: string;
province: string;
city: string;
street: string;
postalCode: string;
}
type Person = {
name: string;
age: number;
address: Address;
}
And there is nothing wrong with the above, especially if you are in control of creating the types in question. But what if you used a code generator, such as the GraphQL Code Generator, for Typescript types? Or if the types come from a third party, say an NPM library you are using.
What do you do in this situation? You can choose to copy the types manually, but this could lead to the maintenance burden of having to sync the types manually and could have unforeseen circumstances if the types were to go out of sync.
The solution for our above conundrum is indexed access types, and they allow us to look up properties of other types and access the types of that properties. The syntax is similar to the syntax of access object keys in Javascript, where we use the key to access the type of the property instead of the value.
Indexed access types allow us to look up specific properties of other types
Let’s go back to our previous example. in Our case, we can create a new Address
type and reference the type of address inside the type Person
.
type Person = {
name: string;
age: number;
address: {
country: string;
province: string;
city: string;
street: string;
postalCode: string;
}
}
type Address = Person["address"]
What about deeply nested types?
We can reference a type to be as deeply nested as we want as long as the property of the type we are looking up exists, as shown in the following examples:
type DeeplyNestedType = {
one: {
two: {
three: Persons
}
}
}
type One = DeeplyNestedType["one"];
type Two = DeeplyNestedType["one"]["two"]
type Three = DeeplyNestedType["one"]["two"]["three"]
And here is what we get back:
What about arrays?
We can achieve the same for arrays, but instead of using a specific key, we will use an arbitrary type number
to access the type of a single item of an array. So, for instance, if the type Persons
above were an array instead of an object, we can do the following:
type Persons = {
name: string;
age: number;
address: {
country: string;
province: string;
city: string;
street: string;
postalCode: string;
}
}[]
type Person = Persons[number]
Here are the results of the above-indexed lookup of our array.
And now we can chain our lookup for a property inside our array item type, as we did previously, as shown below:
type Persons = {
name: string;
age: number;
address: {
country: string;
province: string;
city: string;
street: string;
postalCode: string;
}
}[]
type Address = Persons[number]["address"]
And here are the results of our type Address
above.
We can even do something like this, we lookup a deeply nested property with arrays involved:
type Persons = {
name: string;
age: number;
address: {
country: string;
province: string;
city: string;
street: string;
postalCode: string;
}[]
}[]
type DeeplyNestedType = {
one: {
two: {
three: Persons
}
}
}
type Address = DeeplyNestedType["one"]["two"]["three"][number]["address"][number];
And here are the results for the type of Address
.
Conclusion
In this post, we learned about indexed access types and how they can assist us in looking up types of properties in other types. This is very useful for when the types are either code generated or come from third-party libraries, enabling us to reference types of a specific property of other Types. This lowers the burden of maintaining code by alleviating the need to manually keep the types in sync, which, if not done, could lead to bugs within our application.
Typescript is only as good as the Types we provide for it
Indexed access types can be beneficial if combined with the keyof
and typeof
operators, which can enable you to reference types in variables and lookup types for their properties; to learn more, check out my last post here.
That’s it for me this week; see you next week, and remember to keep learning.