Enum as Parameter in TypeScript

EnumsTypescript

Enums Problem Overview


Isn't it possible to set the type of a parameter to an Enum? Like this:

private getRandomElementOfEnum(e : enum):string{
    var length:number = Object.keys(e).length;
    return e[Math.floor((Math.random() * length)+1)];
}

Following error is thrown by compilation:

> Argument expression expected.(1135)

With any obviously everyhting is alright:

private getRandomElementOfEnum(e : any):string{
    var length:number = Object.keys(e).length;
    return e[Math.floor((Math.random() * length)+1)];
}

This Code works fine. but isn't as elegant and typesafe.

Is there a possibility or a little workaround to define an enum as a parameter?

Enums Solutions


Solution 1 - Enums

It's not possible to ensure the parameter is an enum, because enumerations in TS don't inherit from a common ancestor or interface.

TypeScript brings static analysis. Your code uses dynamic programming with Object.keys and e[dynamicKey]. For dynamic codes, the type any is convenient.

Your code is buggy: length() doesn't exists, e[Math.floor((Math.random() * length)+1)] returns a string or an integer, and the enumeration values can be manually set

Here is a suggestion:

function getRandomElementOfEnum<E>(e: any): E {
	var keys = Object.keys(e),
		index = Math.floor(Math.random() * keys.length),
		k = keys[index];
	if (typeof e[k] === 'number')
		return <any>e[k];
	return <any>parseInt(k, 10);
}

function display(a: Color) {
	console.log(a);
}

enum Color { Blue, Green };
display(getRandomElementOfEnum<Color>(Color));

Ideally, the parameter type any should be replaced by typeof E but the compiler (TS 1.5) can't understand this syntax.

Solution 2 - Enums

You can do better than any:

enum E1 {
	A, B, C
}
enum E2 {
	X, Y, Z
}

function getRandomElementOfEnum(e: { [s: number]: string }): number {
	/* insert working implementation here */
	return undefined;
}

// OK
var x: E1 = getRandomElementOfEnum(E1);
// Error
var y: E2 = getRandomElementOfEnum(window);
// Error
var z: string = getRandomElementOfEnum(E2);

Solution 3 - Enums

I agree with @Tarh. Enums in TypeScript are just Javascript objects without a common interface or prototype (and if they are const enum, then they are not even objects), so you cannot restrict types to "any enum".

The closest I could get is something like the following:

enum E1 {
    A, B, C
}
enum E2 {
    X, Y, Z
}

// make up your own interface to match TypeScript enums
// as closely as possible (not perfect, though)
interface Enum {
	[id: number]: string
}

function getRandomElementOfEnum(e: Enum): string {
   let length = Object.keys(e).length / 2;
   return e[Math.floor((Math.random() * length))];
}

This works for all enums (without custom initializers), but it would also accept other arrays as input (and then fail because the method body relies on the very specific key structure that TypeScript enums have).

So unless you have a real need for such a "generic" function, make typesafe functions for the individual enum types (or a union type like E1|E2|E3) that you actually need.

And if you do have this need (and this might very well be an X-Y-problem that can be solved in a better, completely different way given more context), use any, because you have left typesafe territory anyway.

Solution 4 - Enums

Summing up the previous answers with some new syntax - a generic typesafe function, which works with numeric enums as well as string enums:

function getRandomElementOfEnum<T extends {[key: number]: string | number}>(e: T): T[keyof T] {
  const keys = Object.keys(e);

  const randomKeyIndex = Math.floor(Math.random() * keys.length);
  const randomKey = keys[randomKeyIndex];

  // Numeric enums members also get a reverse mapping from enum values to enum names.
  // So, if a key is a number, actually it's a value of a numeric enum.
  // see https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
  const randomKeyNumber = Number(randomKey);
  return isNaN(randomKeyNumber)
    ? e[randomKey as keyof T]
    : randomKeyNumber as unknown as T[keyof T];
}

Solution 5 - Enums

Another possible option not mentioned above is using the actual values. This is however possible only when you know all the options. This, in my opinion is definitely better than any.

    doSomething(a: string, b: 'this'|'can'|'work'): void {
     //do something
    }

Solution 6 - Enums

May be this trick will fit:

enum AbstractEnum { // put somewhere in hidden scope
}

private getRandomElementOfEnum(e: typeof AbstractEnum) {
    ...
}

Solution 7 - Enums

@selinathat's solution is great only if you have few types. but what if we have more ? for example :

doSomething(a: string, b: 'this'|'can'|'work'|'test1'|'test2'|'test3'): void {
 //do something
}

its pretty ugly hah !? i prefer to use keyof :

interface Items {
    'this',
    'can',
    'work',
    'test1',
    'test2',
    'test3',
}

doSomething(a: string, b: keyof Items): void {
 //do something
}

Solution 8 - Enums

Tested on TypeScript 3.9.7

Solution
type EnumTypeString<TEnum extends string> =
	{ [key in string]: TEnum | string; }

type EnumTypeNumber<TEnum extends number> =
	{ [key in string]: TEnum | number; }
	| { [key in number]: string; }

type EnumType<TEnum extends string | number> =
	(TEnum extends string ? EnumTypeString<TEnum> : never)
	| (TEnum extends number ? EnumTypeNumber<TEnum> : never)

type EnumOf<TEnumType> = TEnumType extends EnumType<infer U>
	? U
	: never
Usage
EnumType:
function forEachEnum<TEnum extends string | number>(
	enumType: EnumType<TEnum>,
	callback: (value: TEnum, key: string) => boolean|void,
) {
	for (let key in enumType) {
		if (Object.prototype.hasOwnProperty.call(enumType, key) && isNaN(Number(key))) {
			const value = enumType[key] as any
			if (callback(value, key)) {
				return
			}
		}
	}
}
EnumOf:
function forEachEnum2<TEnumType>(
	enumType: TEnumType,
	callback: (value: EnumOf<TEnumType>, key: string) => boolean|void,
) {
	for (let key in enumType) {
		if (Object.prototype.hasOwnProperty.call(enumType, key) && isNaN(Number(key))) {
			const value = enumType[key] as any
			if (callback(value, key)) {
				return
			}
		}
	}
}
Tests
enum EnumAsString {
	Value1 = 'value 1',
	Value2 = 'value 2',
}

enum EnumAsNumber {
	Value1 = 1,
	Value2 = 2,
}

// Error
let sn: EnumType<string> = EnumAsNumber

// Correct
let ns: EnumType<number> = EnumAsString // I have not found a solution for the error here
let nn: EnumType<number> = EnumAsNumber
let Nn: EnumType<EnumAsNumber> = EnumAsNumber
let ss: EnumType<string> = EnumAsString
let Ss: EnumType<EnumAsString> = EnumAsString

forEachEnum(EnumAsString, value => {
	let e: EnumAsString = value
	let s: string = value
	let n: number = value // Error
})

forEachEnum(EnumAsNumber, value => {
	let e: EnumAsNumber = value
	let s: string = value // Error
	let n: number = value
})

forEachEnum2(EnumAsString, value => {
	let e: EnumAsString = value
	let s: string = value
	let n: number = value // Error
})

forEachEnum2(EnumAsNumber, value => {
	let e: EnumAsNumber = value
	let s: string = value // Error
	let n: number = value
})

Solution 9 - Enums

Here is an example that allows passing an enum with a typechecked value of that enum using a generic. It's really a response to a slightly different question here that was marked as a duplicate: https://stackoverflow.com/questions/45199509/typescript-how-to-pass-enum-as-parameter

enum Color {
    blue,
};
enum Car {
    cadillac,
};
enum Shape {
    square,
}

type SupportedEnums = typeof Color | typeof Car;

type InvertTypeOf<T> = T extends typeof Color ? Color :
    T extends typeof Car ? Car : never;

function getText<T extends SupportedEnums>(enumValue: InvertTypeOf<T>, typeEnum: T) string | undefined {
  if (typeEnum[enumValue]) {
    return `${enumValue}(${typeEnum[enumValue]})`;
  }
}

console.log(getText(Car.cadillac, Car)); // 0(cadillac)
console.log(getText(0, Color)); // 0(red)
console.log(getText(4, Color)); // undefined

// @ts-expect-error Color is not Car
console.log(getText(Color.blue, Car));

// @ts-expect-error Car is not a Color
console.log(getText(Car.toyota, Color));

// @ts-expect-error  Shape is not in SupportedEnums
console.log(getText(5, Shape));

// @ts-expect-error  Shape is not in SupportedEnums
console.log(getText(Shape.square, Shape));

Solution 10 - Enums

I had the same kind of problem, and i did this

  private getOptionsFromEnum(OptionEnum: Record<string, string>): Array<SelectOption> {
    return Object.keys(OptionEnum).map((value) => {
      return {
        name: OptionEnum[value],
        value,
      } as SelectOption;
    });
  }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPascalView Question on Stackoverflow
Solution 1 - EnumsPaleoView Answer on Stackoverflow
Solution 2 - EnumsRyan CavanaughView Answer on Stackoverflow
Solution 3 - EnumsThiloView Answer on Stackoverflow
Solution 4 - EnumsValeriy KatkovView Answer on Stackoverflow
Solution 5 - EnumsselinathatView Answer on Stackoverflow
Solution 6 - EnumskoldoonView Answer on Stackoverflow
Solution 7 - EnumsMajiDView Answer on Stackoverflow
Solution 8 - EnumsNikolay MakhoninView Answer on Stackoverflow
Solution 9 - EnumsfantapopView Answer on Stackoverflow
Solution 10 - EnumsÁlister Lopes FerreiraView Answer on Stackoverflow