Get the keys of a numeric TypeScript Enum

December 15, 2021TypeScript

4 min read

If you've ever tried getting the keys from a numeric TypeScript Enum, you've probably noticed that it returned both the keys and values of that Enum.

The problem

Here is an example of what happens when we apply Object.keys() on an Enum with implicit numeric values.

enum Players { 
  Mario,
  Luigi,
  Peach,
  Toad,
}

console.log(Object.keys(Players));
// ["0", "1", "2", "3", "Mario", "Luigi", "Peach", "Toad"]

The same thing happens when we define explicit numeric values for a similar Enum and apply Object.keys() to it as well.

enum Characters { 
  Mario = 101,
  Luigi = 102,
  Peach = 103,
  Toad = 104,
}

console.log(Object.keys(Characters));
// ["101", "102", "103", "104", "Mario", "Luigi", "Peach", "Toad"] 

The explanation

If we output the contents of the Players Enum above using console.log(Players), we will see the following.

{
  "0": "Mario",
  "1": "Luigi",
  "2": "Peach",
  "3": "Toad",
  "Mario": 0,
  "Luigi": 1,
  "Peach": 2,
  "Toad": 3
} 

Notice that both the left and right sides of the Enum are defined as keys. Both the numeric values and the names that they are assigned to, are output as keys.

If we take a look at how the Players Enum is complied from TypeScript to JavaScript, we'll see something like this.

var Players;
(function (Players) {
  Players[Players["Mario"] = 0] = "Mario";
  Players[Players["Luigi"] = 1] = "Luigi";
  Players[Players["Peach"] = 2] = "Peach";
  Players[Players["Toad"] = 3] = "Toad";
})(Players || (Players = {}));

The Players["Mario"] = 0 variable assignment creates a property Mario that is assigned a value 0. However, that is not the only variable assignment taking place here. Notice that there is also a Players[Players["Mario"] = 0] = "Mario" variable assignment that creates a property of 0 with the value of Mario.

The solution

In order to get only the actual keys from a numeric TypeScript Enum, we will have to use Object.keys() in combination with the array filter method.

enum Players { 
  Mario,
  Luigi,
  Peach,
  Toad,
}

const keys = Object.keys(Players).filter(player => {
  return !(parseInt(player) >= 0);
});
console.log(keys);
// ["Mario", "Luigi", "Peach", "Toad"] 

In the example above, we are using the bang !, or logical "not", operator within the filter method to retrieve the Player values that fail the parseInt(player) >= 0 test. The values 0, 1, 2, 3 will pass the parseInt(player) >= 0 test, but the bang operator will end up excluding them.

The values Mario, Luigi, Peach, and Toad will result in NaN after parseInt(player) is applied to them. NaN is not greater than or equal to 0, so this test will be false. However, the bang operator will make it true and end up including these values in the result.

We can also use another approach to get the same result.

let keys = Object.keys(Players).filter(player => isNaN(+player));
console.log(keys);
// ["Mario", "Luigi", "Peach", "Toad"] 

In this case, we are also filtering out only the non-numeric values from the Enum to get the actual keys.

Without the unary plus operator (+), we would get a TypeScript error. We used the unary plus operator to make the player values compatible with the definitions in TypeScript for the isNaN() function, which expects a number value. The unary plus operator attempts to convert each player value into a number, if it is not already a number. The iteration for Mario will be +Mario, which results in NaN, thus causing the isNaN() test to pass.

Conclusion

TypeScript Enums can be tricky, especially when numeric values are used. We looked at why Object.keys() does not behave as we would expect it to on these types of Enums. We then looked at a simple solution to get the non-numeric keys from these types of Enums. You are now well-equipped to handle TypeScript Enums with numeric values.

For more help with TypeScript Enums, I have a blog post on how to Loop over a TypeScript Enum that contains string values.