How to merge two enums in TypeScript

TypescriptTypesEnumsMergetypescript2.0

Typescript Problem Overview


Suppose I have two enums as described below in Typescript, then How do I merge them

enum Mammals {
    Humans,
    Bats,
    Dolphins
}

enum Reptiles {
    Snakes,
    Alligators,
    Lizards
}

export default Mammals & Reptiles // For Illustration purpose, Consider both the Enums have been merged.

Now, when I import the exported value in another file, I should be able to access values from both the enums.

import animalTypes from "./animalTypes"

animalTypes.Humans //valid

animalTypes.Snakes // valid

How can I achieve such functionality in TypeScript?

Typescript Solutions


Solution 1 - Typescript

Problems with the merge:

  • same values => values are overwritten

  • same keys => keys are overwritten

  • ❌ Enums with same values (=> values are overwritten)

enum AA1 {
  aKey, // = 0
  bKey // = 1
}
enum BB1 {
  cKey, // = 0
  dKey // = 1
}
  • ❌ Enums with the same keys (=> keys are overwritten)
enum AA2 {
  aKey = 1
}
enum BB2 {
  aKey = 2
}
  • ✅ Good
enum AA3 {
  aKey, // = 0
  bKey // = 1
}
enum BB3 {
  cKey = 2,
  dKey // = 3
}
  • ✅ Also Good
enum AA4 {
  aKey = 'Hello',
  bKey = 0,
  cKey // = 1
}
enum BB4 {
  dKey = 2,
  eKey = 'Hello',
  fKey = 'World'
}

Note: aKey = 'Hello' and eKey = 'Hello' work because the enum with a string value doesn't has this value as key

// For aKey = 'Hello', key is working
type aa4aKey = AA4.aKey; // = AA4.aKey
// value is not.
type aa4aValue = AA4.Hello; // ❌ Namespace 'AA4' has no exported member 'Hello'
type aa4aValue2 = AA4['Hello']; // ❌ Property 'Hello' does not exist on type 'AA4'

console.log(AA4); // { 0: 'bKey', 1: 'cKey', aKey: 'Hello', bKey: 0, cKey: 1 }
console.log(BB4); // { 2: 'dKey', dKey: 2, eKey: 'Hello', fKey: 'World' }

The merge

  • ❌ using union types
type AABB1 = AA4 | BB4; // = AA4 | BB4
type AABB1key = AABB1['aKey']; // = never
type AABB1key2 = AABB1.aKey; // ❌ 'AABB1' only refers to a type, but is being used as a namespace here. ts(2702)
  • ❌ using intersection types
type AABB1 = AA4 & BB4; // = never
type AABB1key = AABB1['aKey']; // = never
  • ✅ using intersection types with typeof
type AABB2 = (typeof AA4) & (typeof BB4); // = typeof AA4 & typeof BB4
type AABB2key = AABB2['aKey']; // = AA4.aKey
  • ✅ using js copy
const aabb1 = { ...AA4, ...BB4 };
const aabb2 = Object.assign({}, AA4, BB4); // also work
// aabb1 = {
// 0: 'bKey',
// 1: 'cKey',
// 2: 'dKey',
// aKey: 'Hello',
// bKey: 0,
// cKey: 1,
// dKey: 2,
// eKey: 'Hello',
// fKey: 'World' }
  • ✅ using typeof with a js copy
const aabb = { ...AA4, ...BB4 };
type TypeofAABB = typeof aabb;
// type TypeofAABB = {
// [x: number]: string;
// dKey: BB4.dKey;
// eKey: BB4.eKey;
// fKey: BB4.fKey;
// aKey: AA4.aKey;
// bKey: AA4.bKey;
// cKey: AA4.cKey;
// };

Tip: you can use the same name for a type and a value

const merged = { ...AA4, ...BB4 };
type merged = typeof merged;

const aValue = merged.aKey;
type aType = merged['aKey'];

Your case

If you want to merge your 2 enums you have ~3 choices:

1. Using string enums

enum Mammals {
  Humans = 'Humans',
  Bats = 'Bats',
  Dolphins = 'Dolphins'
}

enum Reptiles {
  Snakes = 'Snakes',
  Alligators = 'Alligators',
  Lizards = 'Lizards'
}

export const Animals = { ...Mammals, ...Reptiles };
export type Animals = typeof Animals;

2. Using unique numbers

enum Mammals {
  Humans = 0,
  Bats,
  Dolphins
}

enum Reptiles {
  Snakes = 2,
  Alligators,
  Lizards
}

export const Animals = { ...Mammals, ...Reptiles };
export type Animals = typeof Animals;

3. Using nested enums

enum Mammals {
  Humans,
  Bats,
  Dolphins
}

enum Reptiles {
  Snakes,
  Alligators,
  Lizards
}

export const Animals = { Mammals, Reptiles };
export type Animals = typeof Animals;

const bats = Animals.Mammals.Bats; // = 1
const alligators = Animals.Reptiles.Alligators; // = 1

Note: you can also merge the nested enums with the following code. Take care to NOT have duplicated values if you do that!

type Animal = {
  [K in keyof Animals]: {
    [K2 in keyof Animals[K]]: Animals[K][K2]
  }[keyof Animals[K]]
}[keyof Animals];

const animal: Animal = 0 as any;

switch (animal) {
  case Animals.Mammals.Bats:
  case Animals.Mammals.Dolphins:
  case Animals.Mammals.Humans:
  case Animals.Reptiles.Alligators:
  case Animals.Reptiles.Lizards:
  case Animals.Reptiles.Snakes:
    break;
  default: {
    const invalid: never = animal; // no error
  }
}

Solution 2 - Typescript

If you want something behaving like an enum from the way you consume it, you could still use merged object in javascript.

enum Mammals {
    Humans = 'Humans',
    Bats = 'Bats',
    Dolphins = 'Dolphins',
}

enum Reptiles {
  Snakes = 'Snakes',
  Alligators = 'Alligators',
  Lizards = 'Lizards',
}

const Animals = {
   ...Mammals,
   ...Reptiles,
}

type Animals = Mammals | Reptiles

Then you could use Animals.Snakes or Animals.Dolphins and both should be properly typed and work as an enum

Solution 3 - Typescript

Enums, interfaces and types - a working solution for merging enums

What's confusing here is types vs. values.

  • If you define a value (let, const etc.) it will have a value plus some computed but not separately named type.
  • If you define a type or interface, it will create a named type but that will not be outputted or considered in the final JS in any way. It only helps when writing your app.
  • If you create an enum in Typescript, it creates a static type name that you can use plus a real object outputted to JS that you can use.

From the TS handbook: > Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum.

So, if you Object.assign() two enums, it will create a new, merged value (object), but not a new named type.

Since it's not an enum anymore, you lose the advantage of having a value and a named type, but you can still create a separate type name as a workaround.

Fortunately, you can have the same name for the value and the type, and TS will import both if you export them.

// This creates a merged enum, but not a type
const Animals = Object.assign({}, Mammals, Reptiles);

// Workaround: create a named type (typeof Animals won't work here!)
type Animals = Mammals | Reptiles;

TS playground link

Solution 4 - Typescript

A TypeScript enum not only contains the keys you define but also the numerical inverse, so for example:

Mammals.Humans === 0 && Mammals[0] === 'Humans'

Now, if you try to merge them -- for example with Object#assign -- you'd end up with two keys having the same numerical value:

const AnimalTypes = Object.assign({}, Mammals, Reptiles);
console.log(AnimalTypes.Humans === AnimalTypes.Snakes) // true

And I suppose that's not what you want.

One way to prevent this, is to manually assign the values to the enum and make sure that they are different:

enum Mammals {
    Humans = 0,
    Bats = 1,
    Dolphins = 2
}

enum Reptiles {
    Snakes = 3,
    Alligators = 4,
    Lizards = 5
}

or less explicit but otherwise equivalent:

enum Mammals {
    Humans,
    Bats,
    Dolphins
}

enum Reptiles {
    Snakes = 3,
    Alligators,
    Lizards
}

Anyway, as long as you make sure that the enums you merge have different key/value sets you can merge them with Object#assign.

Playground Demo

Solution 5 - Typescript

I'd say the proper way to do it would be defining a new type:

enum Mammals {
    Humans = 'Humans',
    Bats = 'Bats',
    Dolphins = 'Dolphins',
}

enum Reptiles {
  Snakes = 'Snakes',
  Alligators = 'Alligators',
  Lizards = 'Lizards',
}

type Animals = Mammals | Reptiles;

Solution 6 - Typescript

A very simple solution, copied from here

For two different set of enums:

enum WeatherType {
  CLOUDY,
  SUNNY,
  RAIN
}
enum WeatherType {
  CLOUDY = 0,
  SUNNY = 1,
  RAIN = 2
}

You can then just use: type MyMergedEnum = AnEnum & AnotherEnum;

Solution 7 - Typescript

Try this enumerations example ------

Enums or enumerations are a new data type supported in TypeScript

enum PrintMedia {
    Newspaper = 1,
    Newsletter,
    Magazine,
    Book
}

function getMedia(mediaName: string): PrintMedia {
    if (mediaName === 'Forbes' || mediaName === 'Outlook') {
        return PrintMedia.Magazine;
    }
 }

let mediaType: PrintMedia = getMedia('Forbes');

Solution 8 - Typescript

Tagging on a (maybe better) way to do this:

export enum Fruit {
  COCONUT = "COCO",
  BANANA = "BANANA",
}

export enum Vegetable {
  BROCCOLI = "BROCCOLI",
}

export const Foods = {
  ...Fruit,
  ...Vegetable,
};

export type Food = keyof typeof Foods;

Make sure the strings you defined the enums with don't collide. Food is the type, Foods is the underlying map defining the enum (which normally js would make for you). What's cool about this way is:

Foods[Food.BROCCOLI] is the string "BROCCOLI", just like how Fruit[Fruit.COCONUT] is the string "COCO", and with these types the compiler knows that.

So combining Foods and Food you get the standard enum behavior.

Solution 9 - Typescript

You need to use string ids for enum values and right export types:

enum Mammals {
  Humans = 'humans',
  Bats = 'bats',
  Dolphins = 'dolphins',
}

enum Reptiles {
  Snakes = 'snakes',
  Alligators = 'alligators',
  Lizards = 'lizards',
}

export const Animals = { ...Mammals, ...Reptiles };
type TAnimalsKeys = keyof typeof Animals;
export type TAnimal = typeof Animals[TAnimalsKeys];

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbesrabasantView Question on Stackoverflow
Solution 1 - TypescriptThéry FouchterView Answer on Stackoverflow
Solution 2 - TypescriptArnaud BertrandView Answer on Stackoverflow
Solution 3 - TypescriptgombosgView Answer on Stackoverflow
Solution 4 - TypescriptTaoView Answer on Stackoverflow
Solution 5 - TypescriptwaterpleaView Answer on Stackoverflow
Solution 6 - TypescriptAlberto S.View Answer on Stackoverflow
Solution 7 - TypescriptBal mukund kumarView Answer on Stackoverflow
Solution 8 - TypescriptBen OrgeraView Answer on Stackoverflow
Solution 9 - TypescriptArtur SitnikovView Answer on Stackoverflow