s-hunter
s-hunter

Reputation: 25826

How to get all enum values from a single enum value in typescript?

Here is an enum definition

enum MyNumber {
    ONE='ONE',
    TWO='TWO',
    THREE='THREE',
    FOUR='FOUR',
    FIVE='FIVE',
}

This will print all enum values.

const values = Object.values(MyNumber);
console.log(values); // prints ["ONE", "TWO", "THREE", "FOUR", "FIVE"]

If I'm only given a variable myVar, I know it's an enum value, but I don't know what the actual enum is. Is there a way to infer the enum type and get all possible enum values from this variable myVar?

Upvotes: 2

Views: 4026

Answers (2)

Matthew Layton
Matthew Layton

Reputation: 42300

Enums are a TypeScript-only feature, by which I mean, there is no formal definition of an enum in JavaScript. At compile time, the enum itself is erased, and all that is left (for non-const) enums is a simple object; for example...

var MyNumber;
(function (MyNumber) {
    MyNumber["ONE"] = "ONE";
    MyNumber["TWO"] = "TWO";
    MyNumber["THREE"] = "THREE";
    MyNumber["FOUR"] = "FOUR";
    MyNumber["FIVE"] = "FIVE";
})(MyNumber || (MyNumber = {}));

You'll notice there that the object itself, of course has a type (MyNumber), but the members of MyNumber are not of type MyNumber, and this is why you cannot identify the type of an enum from one of its members.

Note: Enums in TypeScript (and even C#) suck! They are just named values, unlike in Java where they are fully qualified objects with enum semantics.

What you could do is implement an enum class, rather than using TypeScript's built-in enum. I've seen this practice used quite a lot in the C# community.

Here is an abstract class for implementing enums...

abstract class Enum {

    protected constructor(public readonly value: number, public readonly name: string) {
    }
    
    public toString(): string {
        return this.name;
    }
    
    public valueOf(): number {
        return this.value;
    }

    public static getAll<T extends Enum>(): T[] {
        const enumeration: {[key:string]:any} = this;
        return Object
            .keys(enumeration)
            .filter(name => enumeration[name] instanceof this)
            .map(name => enumeration[name]);
    }
}

And here is how you would implement it...

class MyNumber extends Enum {
    public static readonly ONE: MyNumber = new MyNumber(1, "ONE");
    public static readonly TWO: MyNumber = new MyNumber(2, "TWO");
    public static readonly THREE: MyNumber = new MyNumber(3, "THREE");
    public static readonly FOUR: MyNumber = new MyNumber(4, "FOUR");
    public static readonly FIVE: MyNumber = new MyNumber(5, "FIVE");

    // Make sure this is private so consumers can't add to or edit your enum!
    private constructor(public readonly value: number, public readonly name: string) {
        super(value, name);
    }
}

And here's a test...

const value = MyNumber.ONE;
const proto: typeof Enum = value.constructor as typeof Enum;

console.log(proto.name);
console.log(proto.getAll());

And the output...

"MyNumber" 

[MyNumber: {
  "value": 1,
  "name": "ONE"
}, MyNumber: {
  "value": 2,
  "name": "TWO"
}, MyNumber: {
  "value": 3,
  "name": "THREE"
}, MyNumber: {
  "value": 4,
  "name": "FOUR"
}, MyNumber: {
  "value": 5,
  "name": "FIVE"
}] 

And the playground: Playground

Note: There are some added benefits to this; for example, currently in TypeScript, you can't do T extends enum, but with this approach, you can do T extends Enum.

Upvotes: 1

E_net4
E_net4

Reputation: 30032

When it comes specifically to TypeScript enums, it is not possible to know from what enum type a value came from. This is because the values of an enum are encoded underneath either as numbers or strings, and do not hold any references to their enum source.

This is more evident when understanding that a (non-const) enum compiles to an object which maps from keys to values and vice versa.

enum MyNumber {
    ONE = 'ONE',
    TWO = 'TWO',
    THREE = 'THREE',
    FOUR = 'FOUR',
    FIVE = 'FIVE',
}
var MyNumber;
(function (MyNumber) {
    MyNumber["ONE"] = "ONE";
    MyNumber["TWO"] = "TWO";
    MyNumber["THREE"] = "THREE";
    MyNumber["FOUR"] = "FOUR";
    MyNumber["FIVE"] = "FIVE";
})(MyNumber || (MyNumber = {}));

Even if the member type is known at compile time, the compiler currently does not provide a type mapping from an enum member to its respective enum type, as of TypeScript v4.8.

If one truly needs to point back to which enum it came from, then one either needs to encode that information alongside the enum value, or use classes instead of enums.

Upvotes: 0

Related Questions