Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge two enums in TypeScript

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?

like image 958
besrabasant Avatar asked Jan 27 '18 17:01

besrabasant


People also ask

Can enum extend another enum TypeScript?

The short answer is no, you can't extend enums because TypeScript offers no language feature to extend them.

Does TypeScript support enums?

Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript. Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.


2 Answers

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   } } 
like image 101
Théry Fouchter Avatar answered Sep 21 '22 16:09

Théry Fouchter


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

like image 41
Arnaud Bertrand Avatar answered Sep 20 '22 16:09

Arnaud Bertrand