TIL: TypeScript Index Signatures Are Super Useful!

A ripe banana on a yellow background

Heads up: I'm new to TypeScript. This is my first entry on the subject and it's entirely possible I'm giving bad advice here. I'm sharing as I learn, and I haven't (yet?) taken the time to read through the TypeScript handbook. Please feel free to help me learn more, and definitely don't take what I say here as any sort of best practice; even if I do get lucky enough to stumble into the truth.

I ran into a problem trying to type some data that represented a collection of other Types that I had massaged into a different shape in order to be useful for my application. It took some googling and reading, but eventually I found the index signatures documentation, which solved my problem.

Let's assume you have an array of Widget objects:

interface Widget {
category: 'thingamajigs' | 'whosawhatsis';
squanches: boolean;
universeId: string;
}

And let's further assume that you need to break that array of Widgets up into a set of named arrays, one per category, and the category name is the key of the object. After doing so, it would look like this:

{
'thingamajigs': [
//Wiget
//Wiget
//Wiget
],
'whosawhatsis': [
//Wiget
//Wiget
]
}

The function that creates this data structure is pretty simple[1]:

function categorizeWidgets(widgets: Widget[]): any {
let groups: any = {};
widgets.forEach((w) => {
const cat = w.category;
groups[cat] ??= [];
groups[cat].push(w);
});
return groups;
}

I've used any here as a placeholder for this imaginary anonymous type that we need to create that defines what we're trying to produce. So my question was, "How do I create a type that indicates that it's an object with arbitrary strings as keys, and arrays of Widgets as values?"

If we take off those any types, we get this error from TypeScript:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.

We can use the any type and call it a day (hey, the error went away!), but that has deleterious effects downstream in the app, since TypeScript won't really know what to expect, and so it's not really offering us much (any? (heh)) benefit when working with this data after it's been massaged. So it would be nice if we knew how to properly type this.

We already have the Widget type, so you might think that you should create a CategorizedWidgetCollection type —and maybe you should, depending on how often you'll use it— but regardless, how do you specify the type of this object?

I started with the basic {} (or Object), but of course TypeScript wasn't too happy with me. The same error comes back.

So here's the answer, given a name only to help it syntax highlight and make sense:

interface CategorizedWidgetCollection {
[index: string]: Widget[];
}

And here's how you might apply it in an anonymous-type fashion:

function categorizeWidgets(widgets: Widget[]): { [index: string]: Widget[] } {
let groups: any = {};
widgets.forEach((w) => {
const cat = w.category;
groups[cat] ??= [];
groups[cat].push(w);
});
return groups;
}

Note that the first line of the method still declares the groups object as any but TS doesn't complain about this because it can see from the input type, the output type, and the logic of the function that the contract is being adhered to.

So the trick was the use of the [index: string] syntax. You're telling TS that the object will be indexed by strings. If we look back at the error message one last time...

... because expression of type 'string' can't be used to index type '{}'.

Hopefully, for myself and for you alike, seeing this error message in the future will jog our memories about this [index:] syntax.


  1. If you're not familiar with the syntax x ??= y, it's shorthand for if (typeof x === 'undefined' || x === null){ x = y }. I only just learned of it today. This is something I've been wanting for at least 10 years! I love how much JS is evolving these days! ↩︎

Webmentions

It's like comments, but you do it on Twitter.

1 Like

Joℏn C. ₿l₳nd II 🤓
Add your comment: Tweet about this article.

Webmentions via webmention.io.

Discuss on TwitterEdit on GitHubContributions