The issue lies in the fact that the types
enum may not always have just two members. It is also unclear which member corresponds to English to Greek mapping and vice versa. How would this function if types
had only one or three members?
An alternative approach is to eliminate the first generic type parameter and rely on hardcoded property names instead, for example:
type MappingForEnum<A extends string, B extends string> = {
convert: {[key in A]: B};
reverse: {[key in B]: A};
};
...
const mapping: MappingForEnum<Greek, English> = {
convert: {
[Greek.ALPHA]: English.A,
[Greek.BETA]: English.B,
},
reverse: {
[English.A]: Greek.ALPHA,
[English.B]: Greek.BETA,
},
};
Alternatively, you can break down the type into smaller one-way mappings and then combine them:
type MappingForEnum<A extends string, B extends string> = { [key in A]: B };
type GreekEnglishMapping = {
toEnglish: MappingForEnum<Greek, English>;
toGreek: MappingForEnum<English, Greek>;
};
...
const mapping: GreekEnglishMapping = {
toEnglish: {
[Greek.ALPHA]: English.A,
[Greek.BETA]: English.B,
},
toGreek: {
[English.A]: Greek.ALPHA,
[English.B]: Greek.BETA,
},
};
By using the second solution, it becomes simple to obtain a union type similar to the original types
enum:
type supportedMappings = keyof GreekEnglishMapping; // "toEnglish" | "toGreek"
Here's another method:
type MappingForEnum<A extends string, B extends string> = { [key in A]: B };
type TwoWayMapping<T extends { [0]: string, [1]: string }, A extends string, B extends string> =
{ [key in T[0]]: MappingForEnum<A, B> } &
{ [key in T[1]]: MappingForEnum<B, A> };
type GreekEnglishMapping = TwoWayMapping<[ 'toEnglish', 'toGreek' ], Greek, English>;
Unfortunately, even if you define
enum MappingNames {
toEnglish,
toGreek,
}
where MappingNames[0]
is defined during runtime, the compiler may not recognize it, causing the following to fail:
type GreekEnglishMapping = TwoWayMapping<MappingNames, Greek, English>;
// Type 'MappingNames' does not satisfy the constraint '{ [0]: string; [1]: string; }'.
One more approach:
type MappingForEnum<A extends string, B extends string> = { [key in A]: B };
type TwoWayMapping<T extends { [k: string]: A | B }, A extends string, B extends string> = {
[key in keyof T]: T[key] extends A ? MappingForEnum<A, B> : MappingForEnum<B, A>
};
type PureMapping = {
toEnglish: Greek,
toGreek: English,
};
type GreekEnglishMapping = TwoWayMapping<PureMapping, Greek, English>;
This is likely as close as you can get to your original requirements. It seems inevitable to specify Greek
/English
twice (once in the mapping type and once as generic arguments).