I have a type like this:
enum Type {
A = 'A',
B = 'B',
C = 'C'
}
type Union =
| {
type: Type.A | Type.B;
key1: string
}
| {
type: Type.C;
key2: string
}
type EnumToUnionMap = {
[T in Type]: {
[k in keyof Extract<Union, {type: T}>]: string
}
}
The issue I'm having is that typeof EnumToUnionMap[Type.A] is never (in reality, it's a generic key signature like [x: string]: string but that's because Extract<Union, {type: T}> returns type never when T is Type.A or Type.B) while typeof EnumToUnionMap[Type.C] is
{
type: Type.C,
key2: string
}
as expected.
This all makes sense because the type in EnumToUnionMap[Type.A] is Type.A | Type.B and Type.A != (Type.A | Type.B) so they don't match and we get never.
Essentially what I need to do something like this:
type EnumToUnionMap = {
[T in Type]: {
[k in keyof Extract<Union, T in Union['type']>]: string
}
}
Why I need to do this:
I receive a response from an alerts endpoint that has this shape:
{
type: Type,
key1: string,
key2: string
}
Both alerts of Type.A and Type.B provide key1 while those of Type.C provide key2.
I need to map the keys in the response to column names in a grid (where some alert types share a common set of keys but have different display names for the columns):
const columnMap: EnumToUnionMap = {
[Type.A]: {
key1: 'Column name'
// Note that in actuality this object will contain
// multiple keys (i.e. multiple columns) for a
// given `Type` so creating a map
// between `Type -> column name` is not possible.
},
[Type.B]: {
key1: 'Different column name'
},
[Type.C]: {
key2: 'Another column name'
}
}
This way, I can do something like the following:
const toColumnText = (alert) => columnMap[alert.type]
...
if (alert.type === Type.A) {
const key1ColumnName = toColumnText(alert).key1 // typed as string
const key2ColumnName = toColumnText(alert).key2 // Typescript warns of undefined key
}