I defined the following enums:
enum myEnum {
a = 1,
b = 2
}
enum mySecondEnum {
c = 3,
d = 4
}
and later on merged them together, to form a single enum:
const mergedEnum = {...myEnum, ...mySecondEnum};
type mergedEnum = typeof mergedEnum;
If I now want to exclude a specific value from the merged enum, I get a strange typescript error, which I can not comprehend:
/** Type '{ [x: number]: string; c: mySecondEnum.c; d: mySecondEnum.d; a: myEnum.a; b: myEnum.b; }' does not satisfy the constraint 'string | number | symbol'.
Type '{ [x: number]: string; c: mySecondEnum.c; d: mySecondEnum.d; a: myEnum.a; b: myEnum.b; }' is not assignable to type 'symbol'.(2344) **/
const mergedOmitedEnum: Record<Exclude<mergedEnum, 1>, number> = {
...
}
**/
However, using the same approach, with a non merged enum, works as expected:
/** This does work as expected, it omits the "1" from "myEnum" */
const simpleOmitedEnum: Record<Exclude<myEnum, 1>, number> = {
"2": 1
}
How can I merge multiple enums, and later on omit some values from the merged result set?
Here's a playground link
CodePudding user response:
The simplest way to create the mergedEnum
type would be to just union the two different enums
type mergedEnum = myEnum | mySecondEnum;
Normally you would want to get type of values in the mergedEnum
constant, typeof mergedEnum
is the type of the constant itself. To get the types of all values normally we could do typeof mergedEnum[keyof typeof mergedEnum]
. But because the enum objects have index signatures from number
to string
, the merged object will also have this signature. Since the original enums are number
enums, we could just remove the extra string
in the union we get from typeof mergedEnum[keyof typeof mergedEnum]
type mergedEnum = Exclude<typeof mergedEnum [keyof typeof mergedEnum ], string>;
CodePudding user response:
Consider this line:
const mergedEnum = { ...myEnum, ...mySecondEnum }
as an anti patterns. enums
are statically known objects, you should not do any operations on them.
You should be aware that compiled version of enum is more wider than code representation. It is bidirectional data structure.
If you still want to merge these data structure, you should use immutable object instead.
const MyEnum = {
a: 1,
b: 2
} as const
const MySecondEnum = {
c: 3,
d: 4
} as const
const MergedEnum = { ...MyEnum, ...MySecondEnum };
type MergedEnum = typeof MergedEnum;
Also, please keep in mind that TS type system does not allow you to create dynamic enum data structure.
If you want to do some type manipulations with enum type you can try this:
enum MyEnum {
a = 1,
b = 2
}
type Enum = `${MyEnum}`
type EnumType = Record<string | number, string | number>
type EnumToObj<Enum extends EnumType> = Pick<
{
[Prop in keyof Enum]:
(Enum[Prop] extends string | number
? `${Enum[Prop]}`
: never)
}, keyof Enum
>
// type Result = {
// readonly a: "1";
// readonly b: "2";
// }
type Result = EnumToObj<typeof MyEnum>
If you are interested in bidirectional type, check this:
enum MyEnum {
a = 1,
b = 2
}
type Enum = `${MyEnum}`
type EnumType = Record<string | number, string | number>
type ToBidirectional<Obj extends EnumType> = {
[Prop in keyof Obj as Obj[Prop]]: Prop
}
type EnumToObj<Enum extends EnumType> = Pick<
{
[Prop in keyof Enum]:
(Enum[Prop] extends string | number
? `${Enum[Prop]}`
: never)
}, keyof Enum
>
type Bidirectional<T extends EnumType> = T & ToBidirectional<T>
type PseudoEnum = Bidirectional<EnumToObj<typeof MyEnum>>
// type Test = {
// readonly a: "1";
// readonly b: "2";
// readonly 1: "a";
// readonly 2: "b";
// }
type Test = {
[Prop in keyof PseudoEnum]: PseudoEnum[Prop]
}
Now it is easy to omit keys:
type CustomType=Omit<PseudoEnum, 1>
More information about using type representation of enum you can find in my article