I have some Typescript Interfaces that have many repeated, similar fields. For example:
interface Foo {
person1Name: string;
person1Address: string;
person2Name: string;
person2Address: string;
category: string;
department: string;
}
I’m trying to shorten that by using Typescript index signature templates which works great using things like [key: 'person${number}Name']
. However, I’d like to limit the number of allow keys to say 5 ‘person’ properties and it’s not working as expected.
type AllowedIndex = 1 | 2 | 3 | 4 | 5;
interface Foo {
[key: `person${AllowedIndex}Name`]: string;
[key: `person${AllowedIndex}Address`]: string;
category: string;
department: string;
}
The above gives me the error An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.ts(1337)
.
Is there a way to do what I’m trying to do? Or am I stuck repeating the keys the way I already am?
Here is a TS Playground Link with what I’ve tried.
2
Answers
You could achieve this via something like this:
See TS Playground link.
TypeScript 4.4 introduced the ability to use pattern or placeholder template literal types (as implemented in microsoft/TypeScript#40598) in index signatures. But
`person${AllowedIndex}Name`
isn’t a pattern template literal type. In fact, it’s not even a template literal type at all, in the sense that it gets eagerly evaluated by TypeScript to be the union of string literal types"person1Name" | "person2Name" | "person3Name" | "person4Name" | "person5Name"
. And you can’t use a union of string literal types in an index signature.Instead, you can use mapped types to make an object type whose keys are a union of string literals. That would look like
{[K in `person${AllowedIndex}Name`]: string}
if you want the keys to be required, or{[K in `person${AllowedIndex}Name`]?: string}
if you want them to be optional. Equivalently you can use a combination of thePartial
andRecord
utility types likePartial<Record<`person${AllowedIndex}Name`, string>>
.Note that mapped types are their own types and you cannot add properties to them. If you want to combine several mapped types you can use an intersection. Or, if you want to end up with an interface, you can use interface extension to combine the parent mapped types (but only through a named type like
Partial
orRecord
) and your added properties. That giveswhich you can inspect to make sure it behaves as desired:
Playground link to code