What is the type of an enum in Typescript?

Atrahasis picture Atrahasis · May 3, 2018 · Viewed 14.4k times · Source

Given a function that has an argument supposed to be an enum. The enum passed in can have different numbers of properties. How to fix the type of that argument ? enum itself is not a type.

E.g. :

function (myEnum: mysteriousType){
  //What is that mysteriousType ?
}


The use case is to build a generic method to instantiate dat.GUI options from an enum, whatever the string/number type in the enum.

Answer

jcalz picture jcalz · May 3, 2018

With what you've stated so far (needs to accept all string / numeric / heterogeneous enums), the closest I can do is something like this:

type Enum<E> = Record<keyof E, number | string> & { [k: number]: string };
function acceptEnum<E extends Enum<E>>(
  myEnum: E
): void { 
  // do something with myEnum... what's your use case anyway?
}

enum E { X, Y, Z };
acceptEnum(E); // works

I'm not sure what you're going to do with myEnum if all you know is that it's "some enum type", but I guess that's up to you to figure out.


How I came up with this: I examined a bunch of concrete enum types, and they seem to have properties with string keys and string or numeric values (the forward mapping), as well as a numeric index key with string values (the reverse mapping for numeric values).

const works: { X: 0, Y: 1, Z: 2, [k: number]: string } = E; // works

The language designers might have constrained this further, since the reverse mapping will only produce the specific numeric keys and string values seen in the forward mapping, but for some reason it's not implemented like that:

const doesntWork: { X: 0, Y: 1, Z: 2, [k: number]: 'X' | 'Y' | 'Z' } = E; // error
const alsoDoesntWork: { X: 0, Y: 1, Z: 2, 0: 'X', 1: 'Y', 2: 'Z' } = E; // error

So the tightest constraint I can put on an enum type is the above E extends Enum<E>.


Note that this code does not work for const enum types which don't really exist at runtime:

const enum F {U, V, W};
acceptEnum(F); // nope, can't refer to `F` by itself

And also note that the above type (E extends Enum<E>) allows some things it maybe shouldn't:

acceptEnum({ foo: 1 }); // works

In the above, {foo: 1} is plausibly a numeric enum similar to enum Foo {foo = 1} but it doesn't have the reverse mapping, and if you rely on that things will blow up at runtime. Note that {foo: 1} doesn't seem to have an index signature but it still matches an index signature implicitly. It wouldn't fail to match unless you added some explicit bad value:

acceptEnum({foo: 1, 2: 3}); // error, prop '2' not compatible with index signature

But there's nothing to be done here. As I mentioned above, the implementation of enum typing currently does not constrain the numeric keys as much as it can, so there seems to be no way at compile time to distinguish between an enum with a good reverse mapping and one without one.


Hope that helps. Good luck!