1

I'm trying to define a type based on the value of an enum.

enum E {
    A = "apple",
    B = "banana",
    C = "cherries"
}

// type EnumKey = "A" | "B" | "C"
type EnumKey = keyof typeof E

/**
 * Error:
 * Property 'A' does not exist on type 'E'.
 * Property 'B' does not exist on type 'E'.
 * Property 'C' does not exist on type 'E'.
 */
type EnumValue = E[EnumKey]

// type EnumValue2 = E
type EnumValue2 = (typeof E)[EnumKey]

type EnumValueExpected = "apple" | "banana" | "cherries"

In the output js, these properties do exist on E:

"use strict";
var E;
(function (E) {
    E["A"] = "apple";
    E["B"] = "banana";
    E["C"] = "cherries";
})(E || (E = {}));

I don't understand why ts reports an error, how can I get EnumValueExpected?

Jin
  • 53
  • 1
  • 6
  • Try a const enum – Daniel A. White Aug 01 '23 at 01:12
  • `type EnumValue = \`${E}\`` is "how" (but really, if you care about the literal enum values you might want to use a const instead of an enum). As for "why", it's because you're not doing what you're trying to do... `E` is already the union of enum values, so it has no keys you care about, and `(typeof E)[EnumKey]` is just another way of saying `E`. Does that fully address the question or am I missing something? – jcalz Aug 01 '23 at 01:13
  • A const enum doesn't work, the error persists. – Jin Aug 01 '23 at 02:36
  • `${E}` is what I want.Thanks for your help both. – Jin Aug 01 '23 at 02:38

1 Answers1

0

Given the enum

enum E {
    A = "apple",
    B = "banana",
    C = "cherries"
}

your original version didn't work primarily because you were indexing into the E type, which is not the same as the E value (whose type is typeof E and not E). See Generic type to get enum keys as union string in typescript? and its answer for more information about this.


The type you're looking for is very close to just E. Both E and "apples" | "banana" | "cherries" are the union of literal types of the enum values. But E treated as a special subtype of the literal type, specifically so you can't just write, say, "cherries" in place of E.C.

In some sense, the whole point of an enum is to abstract away the actual literal types of the values. If you actually care about these literal types, you might want to avoid enum entirely in favor of an object of the same shape:

const EObject = {
  A: "apple",
  B: "banana",
  c: "cherries"
} as const;

type EKey = keyof typeof EObject;
// type EKey = "A" | "B" | "c"
type EValue = (typeof EObject)[EKey];
// type EValue = "apple" | "banana" | "cherries"

That as const asks the compiler to keep track of the string literal types of the object values, so you can extract both the keys and the value types easily.

But maybe your use case doesn't allow this.


If you really want to extract "apple" | "banana" | "cherries" from E, you can use template literal types to ask "what happens when I serialize E as a string"?

enum E {
  A = "apple",
  B = "banana",
  C = "cherries"
}

type EValue = `${E}`;
// type EValue = "apple" | "banana" | "cherries"

That produces the desired union. Note that if E were a numeric enum or a mixed enum, you'd end up with a string version of the numbers.

enum F {
  D = "durian",
  E = 123,
  F = "figs",
  G = 456
}
type FValueOops = `${F}`
// type FValueOops = "durian" | "figs" | "123" | "456"

If such things matter you could more effort teasing numeric and string literals out:

type EnumToLiteral<T extends string | number> =
  T extends string ? `${T}` : `${T}` extends `${infer N extends number}` ? N : never;

type FValue = EnumToLiteral<F>;
// type FValue = "durian" | 123 | "figs" | 456

But I'd advise pursuing an as const object instead of an enum before doing this sort of type juggling.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360