Home > front end >  How can I exclude some items from a merged enum in TypeScript?
How can I exclude some items from a merged enum in TypeScript?

Time:02-10

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;

Playground Link

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>;

Playground Link

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>

Playground

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]
}

Playground

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

  • Related