Introduction: The Eternal Question on Stack Overflow
Have you ever wondered how experienced TypeScript developers get the names of enum values? If you've browsed Stack Overflow, you've likely seen the question with thousands of views: "How to get names of enum entries?". It seems simple, right? But in practice, many beginners (and not only them) stumble on this.
In this article, we won't just give you a ready-made answer. We'll dive under the hood of TypeScript and break down exactly how enum works, how the compiler transforms them into JavaScript, and why some approaches to getting names can lead to unexpected bugs. Ready? Let's go!
What is an enum in TypeScript and why do you need their names?
An enum (enumeration) is a convenient way to define a set of named constants. In TypeScript, they can be numeric (by default) and string. But what if you need to get the name of a specific value? For example, you receive a status code 403 from an API, and you want to display a user-friendly message like "Forbidden".
enum HttpStatus { OK = 200, NotFound = 404, Forbidden = 403}
// Our task: get the name "Forbidden" from the value 403It seems like TypeScript should provide a built-in method for this. But it doesn't. You have to write your own logic. And that's where the fun begins.
The "Brute Force" Solution: Object.keys and Object.values
The most obvious way is to use Object.keys() or Object.values(). Many beginners write something like this:
function getEnumName(enumObj: any, value: number): string | undefined { const keys = Object.keys(enumObj); return keys.find(key => enumObj[key] === value);}
console.log(getEnumName(HttpStatus, 403)); // "Forbidden" — works!Looks logical, right? Yes. But if you try this code on a numeric enum, you're in for a surprise. Let's see what our HttpStatus compiles to.
Under the Hood: Numeric Enum Compilation
When TypeScript compiles a numeric enum, it creates an object with reverse mapping. Here's what the compiled JavaScript looks like:
var HttpStatus;(function (HttpStatus) { HttpStatus[HttpStatus["OK"] = 200] = "OK"; HttpStatus[HttpStatus["NotFound"] = 404] = "NotFound"; HttpStatus[HttpStatus["Forbidden"] = 403] = "Forbidden";})(HttpStatus || (HttpStatus = {}));
// The resulting HttpStatus object looks like this:// { // "200": "OK", // "404": "NotFound", // "403": "Forbidden", // "OK": 200, // "NotFound": 404, // "Forbidden": 403 // }See the trick? The compiler adds both numeric keys and string keys. Therefore, Object.keys(HttpStatus) will return not only ["OK", "NotFound", "Forbidden"], but also ["200", "404", "403"]. This breaks the logic of our simple solution!
To work around this, you need to filter out numeric keys:
function getEnumNameSafe(enumObj: any, value: number): string | undefined { const keys = Object.keys(enumObj).filter(key => isNaN(Number(key))); return keys.find(key => enumObj[key] === value);}But this code looks hacky. Is there a better way?
String Enums: A Different Story
If you use string enums, reverse mapping is not created. This simplifies the task:
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT"}
// Object.keys(Direction) will return only ["Up", "Down", "Left", "Right"] — perfect!But what if you need to get the name from a value? The same Object.keys works great:
function getStringEn