Map Typescript Enum
The function to solve this is quite simple.
// you can't use "enum" as a type, so use this.type EnumType = { [s: number]: string };function mapEnum (enumerable: EnumType, fn: Function): any[] { // get all the members of the enum let enumMembers: any[] = Object.keys(enumerable).map(key => enumerable[key]); // we are only interested in the numeric identifiers as these represent the values let enumValues: number[] = enumMembers.filter(v => typeof v === "number"); // now map through the enum values return enumValues.map(m => fn(m));}
As you can see, we first need to get all of the keys for the enum (MyEnum.Hello
is actually 1
at runtime) and then just map through those, passing the function on.
Using it is also simple (identical to your example, although I changed the name):
enum MyEnum { Hello, Goodbye };let results = mapEnum(MyEnum, v => { if (v === MyEnum.Hello) { return ':)'; } else if (v === MyEnum.Goodbye) { return ':('; }});console.log(results); // [ ':)', ':(' ]
The reason we need to filter the enum to be numbers only is because of the way enums are compiled.
Your enum is actually compiled to this:
var MyEnum;(function (MyEnum) { MyEnum[MyEnum["Hello"] = 0] = "Hello"; MyEnum[MyEnum["Goodbye"] = 1] = "Goodbye";})(MyEnum || (MyEnum = {}));;
However we are not interested in "Hello"
or "Goodbye"
as we can't use those at runtime.
You will also notice a funny type
statement right before the function. This is because you can't type a parameter as someParameter: enum
, you need to explicitly state it as a number -> string
map.
To map an enum do this:
(Object.keys(MyEnum) as Array<keyof typeof MyEnum>).map((key) => {})
With ts-enum-util
(npm, github), it's easy, type-safe (uses generics), and takes care of skipping the numeric reverse lookup entries for you:
import { $enum } from "ts-enum-util";enum MyEnum { Hello, Goodbye };$enum(MyEnum).map(v => { if (v === MyEnum.Hello) { return ':)'; } else if (v === MyEnum.Goodbye) { return ':('; }}); // produces [':(', ':)']
NOTE: ts-enum-util
always iterates based on the order of the sorted enum keys to guarantee consistent order in all environments. Object.keys() does not have a guaranteed order, so it's impossible to iterate enums "in the order they were defined" in a cross-platform guaranteed way.(update: new version of ts-enum-util now preserves the original order in which the enum was defined)
If you are using string enums, then combine it with ts-string-visitor
(npm, github) for even more generic type-safe compiler checks to guarantee that you handle all possible enum values in your map function:(update: new version of ts-enum-util now includes functionality of ts-string-visitor, and it works on numeric enums now too!)
import { $enum } from "ts-enum-util";import { mapString } from "ts-string-visitor";enum MyEnum { Hello = "HELLO", Goodbye = "GOODBYE" };$enum(MyEnum).map(v => { // compiler error if you forget to handle a value, or if you // refactor the enum to have different values, etc. return mapString(v).with({ [MyEnum.Hello]: ':)', [MyEnum.Goodby]: ':(' });}); // produces [':(', ':)']