Listing all the possible values for an enum in Typescript

Introduction

Today I have spent quite a few hours trying to contribute a P.R. to a project I really like, and often use in my day-to-day work, the excellent class-validator. The problem I was trying to solve consisted in listing the possible values for a given enum, in order to let class-validator print them in its enum-related error messages.

The main problem is that enums get transpiled differently, based on their contents and the way they're declared.

Implicit values

When values are not specified, an incremental integer is assigned to each key, so the transpiled JS looks like the following:

// Before
enum Steak {
    Medium,
    Rare,
    WellDone
}

// After
var Steak;
(function (Steak) {
    Steak[Steak["Medium"] = 0] = "Medium";
    Steak[Steak["Rare"] = 1] = "Rare";
    Steak[Steak["WellDone"] = 2] = "WellDone";
})(Steak || (Steak = {}));

Integer values

When an integer number is assigned as the value, it is simply assigned instead of the default incremental integer in the example before.

// Before
enum Steak {
    Medium = 10,
    Rare = 20,
    WellDone = 30
}

// After
var Steak;
(function (Steak) {
    Steak[Steak["Medium"] = 10] = "Medium";
    Steak[Steak["Rare"] = 20] = "Rare";
    Steak[Steak["WellDone"] = 30] = "WellDone";
})(Steak || (Steak = {}));

String values

When a string value is assigned, though, the transpiled JS code looks a bit different:

// Before
enum Steak {
    Medium = "MEDIUM",
    Rare = "RARE",
    WellDone = "WELL_DONE"
}

// After
var Steak;
(function (Steak) {
    Steak["Medium"] = "MEDIUM";
    Steak["Rare"] = "RARE";
    Steak["WellDone"] = "WELL_DONE";
})(Steak || (Steak = {}));

These transpilation differences lead to obvious inconsistencies when attempting to retrieve an enum's list of possible values when using the common Object.values(), Object.keys() and Object.entries() methods. By fiddling with an heterogeneous enum like the following:

// Before
enum TestEnum {
	a = 'aA',
	b = 'bB',
	c = 1,
	d = 2,
	e = '01'	
}

// After
var TestEnum;
(function (TestEnum) {
    TestEnum["a"] = "aA";
    TestEnum["b"] = "bB";
    TestEnum[TestEnum["c"] = 1] = "c";
    TestEnum[TestEnum["d"] = 2] = "d";
    TestEnum["e"] = "01";
})(TestEnum || (TestEnum = {}));

Conclusions

The following conclusions can be drawn:

  • Object.values() returns an array containing all the possible values for the given enum AND the keys for the properties which have integer numbers as values ([ 'c', 'd', 'aA', 'bB', 1, 2, '01' ])
  • Object.keys() returns an array containing all the enum's keys plus all of the integer values defined ([ '1', '2', 'a', 'b', 'c', 'd', 'e' ])
  • Object.entries() returns an array of [key, value] arrays, plus N [value, key] entries for N of the enum's properties which have integer numbers as values ([ [ '1', 'c' ], [ '2', 'd' ], [ 'a', 'aA' ], [ 'b', 'bB' ], [ 'c', 1 ], [ 'd', 2 ], [ 'e', '01' ] ])

Solution

So, finally, the solution I came up with is:

Object.entries(TestEnum).filter(entry => !parseInt(entry[0])).map(entry => entry[1])

Which starts from Object.keys()'s results, filtering out all the redundant [value, key] entries.

Publication date: 28/01/2021