Reputation: 404
const source = [
{ group: 'first', key: 'A' },
{ group: 'first', key: 'B' },
{ group: 'second', key: 'A' }
] as const;
type DerivedGroupKeys = `${typeof source[number]['group']}.${typeof source[number]['key']}`;
//gives "first.A" | "first.B" | "second.A" | "second.B"
type HardCodedGroupKeys = 'first.A' | 'first.B' | 'second.A';
//gives "first.A" | "first.B" | "second.A"
I would like the DerivedGroupKeys
to be the same as HardCodedGroupKeys
(without hardcoding). I am almost there but it is giving me every possible combination of group and key rather than just the combinations defined in the array. Is this possible?
Upvotes: 1
Views: 1000
Reputation: 10345
This behavior is expected, to quote the handbook:
For each interpolated position in the template literal, the unions are cross multiplied:
What you want is possible, but involves a couple of extra steps. If you think about it, when you index a tuple with number
, the resulting type can be any of the types of tuple members (hence, the union). What you need is to narrow the index type to a numeric literal: if you index a tuple with a literal N, the resulting type can only be the type of the Nth member of the tuple (hence, no union).
First, we can get a union of the indices of the tuple:
type Indices<A> = Exclude<keyof A, keyof any[]>;
Next, simply create a mapped type from tuple with "keys" as tuple indices and "values" as the desired output. Finally, just index the mapped type with tuple indices:
type DerivedGroupKeys = { [ I in Indices<typeof source> ] : `${typeof source[I]['group']}.${typeof source[I]['key']}` }[Indices<typeof source>];
Upvotes: 2
Reputation: 25936
The docs of Template Literal Types say that:
When a union is used in the interpolated position, the type is the set of every possible string literal that could be represented by each union member:
To overcome this issue, you can map each array element separately, instead of mapping the union.
const source = [
{ group: 'first', key: 'A' },
{ group: 'first', key: 'B' },
{ group: 'second', key: 'A' }
] as const;
type ToLiteral<T> =
T extends {group: infer G, key: infer K} ?
G extends string ?
K extends string ? `${G}.${K}` : never
: never
: never;
type SourceElemsType = typeof source[number]
type SourceElesLiterals = ToLiteral<SourceElemsType>
Upvotes: 2