enum typescript return specific value

This is tricky question. Please see related question and my article.

Runtime representation of enum is bidirectional object.

const Test = {
  0: "type1",
  1: "type2",
  2: "type3",
  3: "type4",
  4: "type5",
  type1: 0,
  type2: 1,
  type3: 2,
  type4: 3,
  type5: 4,
}

Please keep this in mind during reading.

COnsider this example:


enum MyEnum {
  ONE, // 0
  TWO // 1
}

// This utility type reverses object. 
// All keys become values an all values become keys
type ReverseObj<T extends Record<string, string | number>> = {
  [Prop in keyof T as T[Prop]]: Prop
}

{
  type _ = ReverseObj<{ age: 42 }> // { 42: "age" }
}

// Type representation of runtime enum value
// Please keep in mind that enum runtime value is bidirectional
// this is why I have used ReverseObject
type EnumToObj = Pick<
  {
    [Prop in keyof typeof MyEnum]: `${typeof MyEnum[Prop]}`
  }, keyof typeof MyEnum
>

type IsKeyValid<
  InitialValue extends number,
  > =
  `${InitialValue}` extends keyof ReverseObj<EnumToObj>
  ? InitialValue
  : never

{
  type _ = IsKeyValid<1> // 1
  type __ = IsKeyValid<2> // never
}

function handleEnum<
  Index extends number,
  >(index: IsKeyValid<Index>): `${Index}` extends keyof ReverseObj<EnumToObj> ? ReverseObj<EnumToObj>[`${Index}`] : never
function handleEnum<
  Index extends number,
  >(index: IsKeyValid<Index>) {
  return MyEnum[index]
}

handleEnum(0) // "ONE"
handleEnum(1) // "TWO"
handleEnum(2) // expected error
handleEnum('ONE') // expected error
handleEnum(0.1) // expected error
handleEnum(NaN) // expected error
handleEnum(Infinity) // expected error

Playground

Some explanation you will find in comments. The goal is to make illegal state unrepresentable.

IsKeyValid - assures that you are not allowed to provide keys which are not exists in enum. For instance, you are not allowed to call handleEnum(10) because we have only two keys, hence allowed keys are 0 | 1. As for the return value, I have obtained it from reversed object ReverseObj.

Some thing you should be aware of:

  1. Types typeof MyEnum and MyEnum are not equal.
  2. More explanation about using as in [Prop in keyof T as T[Prop]]: Prop you can find here
  3. It worth using simple tuple instead of enum if you don't use custom initial indexes for enum. COnsider this example:
const TUPLE = ['one', 'two', 'three'] as const;

type Tuple = typeof TUPLE

type AllowedIndex<Index extends number> = `${Index}` extends Exclude<keyof Tuple, keyof ReadonlyArray<any>> ? Index : never

const getter = <Index extends number>(index: AllowedIndex<Index>) =>
  TUPLE[index]

getter(1) // ok, returns "two"
getter(23) // expected error
  1. In most cases there is even better to use immutable object instead of enum.
const FakeEnum = {
  a: 0,
  b: 1
} as const

It is easy to work with FakeEnum object. Because you can easily, without any tricks obtain keys and appropertiate values of object.

IMHO, I don't think that enum is the best option to use in this case

P.S. If you don't like zero based index in enums, you can set your initial value:

enum MyEnum {
  ONE = 1, // 1
  TWO // 2
}

UPDATE

Above approach has his own drawbacks. As you might have noticed, all utility types are binded with MyEnum. They are not generic. If you are interested in generic solution where you can pass any enum, consider this example:


enum MyEnum {
  ONE, // 0
  TWO // 1
}

/**
 * Obtains a union of all dictionary values
 */
type Values<T> = T[keyof T]


/**
 * Represents any enum type
 */
type EnumType = Record<string | number, string | number>

type EnumToObj<Enum extends EnumType> = Pick<
  {
    [Prop in keyof Enum]:
    (Enum[Prop] extends string | number
      ? `${Enum[Prop]}`
      : never)
  }, keyof Enum
>

{
  // {
  //   readonly ONE: "0";
  //   readonly TWO: "1";
  // }
  type Test = EnumToObj<typeof MyEnum>
}

type GetEnumValue<
  Enum extends EnumType,
  Index extends number,
  Obj extends EnumToObj<Enum> = EnumToObj<Enum>
  > =
  {
    [Prop in keyof Obj]:
    (`${Index}` extends Obj[Prop]
      ? Prop
      : never)
  }[keyof Enum];

{
  type Test = GetEnumValue<typeof MyEnum, 1> // TWO
}

type IsNever<T> = [T] extends [never] ? true : false

type IsKeyValid<
  Index extends number,
  Enum extends EnumType
  > =
  IsNever<GetEnumValue<Enum, Index>> extends true
  ? never
  : Index

{
  type _ = IsKeyValid<1, typeof MyEnum> // 1
  type __ = IsKeyValid<2, typeof MyEnum> // never
}

function handleEnum<
  Index extends number,
  Enum extends EnumType
>(
  enEnum: Enum,
  index: IsKeyValid<Index, Enum>
): GetEnumValue<Enum, Index>
function handleEnum<
  Index extends number,
  Enum extends EnumType
>(enEnum: Enum, index: IsKeyValid<Index, Enum>) {
  return enEnum[index]
}

const x = handleEnum(MyEnum, 0) // "ONE"
const y = handleEnum(MyEnum, 1) // "TWO"

handleEnum(MyEnum, 2) // expected error
handleEnum(MyEnum, 'ONE') // expected error


Playground


TypeScript enums are accessible though there number index on runtime, so

return Test[1];

Will return index 1 from the Test enum.


Since you want 1 to return type1, we'll need to subtract 1 from your num to get the desired index:

enum Test {
   'type1',
   'type2',
   'type3',
   'type4',
   'type5'
}

function getType(num: number) {
    return Test[num - 1];
}   

console.log(getType(1));

Will output:

type1

Try it online!