80

I have an enum:

export enum ApiMessages {
    logged_ok = 'Logged OK',
    register_ok = 'Register OK'
}

I have a function with the enum as a parameter:

export function responseOK(message: ApiMessages, result ?: any): ApiResponse {
    return {
        "status": "ok",
        "code": 200,
        "messageId": ApiMessages[message], <-- KO TS7015
        "message": message,
        "result": result
    };
}

I am calling the function like that:

responseOK(ApiMessages.logged_ok, {user: userRes})

I am trying to return the enum key and the enum string value to the response but I get the TS error:

TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'.

I have strict TypeScript config. Adding suppressImplicitAnyIndexErrors is not an option.

TypeScript version: 2.9.2

2
  • 1
    Hi. In your example message is the value of the enum and not the key. So message is Logged OK and your messageId would be undefined. Btw, messageId is in your example not a number Commented Jan 21, 2019 at 21:15
  • ApiMessages.logged_ok === 'Logged OK'. in your function message is the string you want to send as the message. ApiMessages.logged_ok is the actual value of enum already! Commented Apr 26, 2019 at 20:11

9 Answers 9

93

As described in the handbook:

Keep in mind that string enum members do not get a reverse mapping generated at all.

That means there is no simple reverse mapping in your case.

Workaround: Getting a reverse mapping for string enum members

To get the key of an enum member by its value, you have to iterate through the enum keys and compare the associated value with your target value.

function getEnumKeyByEnumValue(myEnum, enumValue) {
    let keys = Object.keys(myEnum).filter(x => myEnum[x] == enumValue);
    return keys.length > 0 ? keys[0] : null;
}

You can type this more strictly as follows (note that we can interpret our enum as an indexable type with key and value both being strings here):

function getEnumKeyByEnumValue<T extends {[index:string]:string}>(myEnum:T, enumValue:string):keyof T|null {
    let keys = Object.keys(myEnum).filter(x => myEnum[x] == enumValue);
    return keys.length > 0 ? keys[0] : null;
}

Some demo code follows. You can also see it in action on the TypeScript Playground

enum ApiMessages {
    logged_ok = 'Logged OK',
    register_ok = 'Register OK'
}

let exampleValue = ApiMessages.logged_ok;
let exampleKey = getEnumKeyByEnumValue(ApiMessages, exampleValue);

alert(`The value '${exampleValue}' has the key '${exampleKey}'`)

function getEnumKeyByEnumValue<T extends {[index:string]:string}>(myEnum:T, enumValue:string):keyof T|null {
    let keys = Object.keys(myEnum).filter(x => myEnum[x] == enumValue);
    return keys.length > 0 ? keys[0] : null;
}

Adding this into your responseOK() you end up with:

function responseOK(message: ApiMessages, result ?: any) {
    return {
        "status": "ok",
        "code": 200,
        "messageId": getEnumKeyByEnumValue(ApiMessages, message),
        "message": message,
        "result": result
    };
}
Sign up to request clarification or add additional context in comments.

5 Comments

Oh, good to know. Thank you very much for the detailed answer and the demo. The workaround works like a charm.
My compiler complains about myEnum and enumValue being implicitly of type any.
@PhilippMichalski Good catch. I updated my answer with a more strictly typed function. I guess the old TypeScript Playground had different compiler settings at the time.
I tried typing them myself for about an hour and failed miserable due to being new to TypeScript and these type defs are kinda black magic to me. Thank you for updating!
Would this be improved by .find instead of .filter? Terminating the loop, and then test to see if keys (or better, key) is truthy?
28

If you have a string (or any other value) that you know is a valid enum value, you can just tell the compiler to treat the value as an enum.

NOTE: This is only works if you know that your value is valid for the enum.

enum Fruit {
    Apple = 'apple',
    Bananna = 'bananna'
}

const str: string = 'apple';

const fruit: Fruit = str as Fruit;

if (fruit === Fruit.Apple)
    console.log("It's an apple");

if (fruit === Fruit.Bananna)
    console.log("It's a bananna");

Output: It's an apple

2 Comments

you're right!, we just need to do as MyEnum and that's it!.
Prolly fruit in Fruit && fruit === Fruit.Apple can make sure the value is a valid enum?
15

You can easily create a map that allows you to get the key from the value without creating a special function for it.

export enum ApiMessage {
    logged_ok = 'Logged OK',
    register_ok = 'Register OK'
}

export type ApiMessageKey = keyof typeof ApiMessage;

export const API_MESSAGE_KEYS = new Map<ApiMessage, ApiMessageKey>(
    Object.entries(ApiMessage).map(([key, value]:[ApiMessageKey, ApiMessage]) => [value, key])
)

API_MESSAGE_KEYS.get(ApiMessage.logged_ok); // 'logged_ok'

4 Comments

For me, this one seems to be the accurate answer, however, should be stated that "There's no reverse mapping".
Hi @RooticalV., can you explain what you mean by "there's no reverse mapping"?
Typescript throws an error saying Type 'string' is not assignable to type '"logged_ok" | "register_ok"'.
@RobertHung Can you provide a typescript playground demonstrating this error? I might be able to identify the issue.
15

You can now get the traits and their keys of enums within types in TypeScript🎉🎉

 enum ApiMessage {
    logged_ok = 'Logged OK',
    register_ok = 'Register OK'
}

// "logged_ok" | "register_ok"
type ApiMessageTraits = keyof typeof ApiMessage;

// "Logged OK" | "Register OK"
type MessageContents = `${ApiMessage}`;

Sadly, this relies on casting the keys of the enumerables into a string, so you can't have any other type of keys except for a string.

Edit: Simplified second method greatly by using template literals w/ TypeScript >=4.1.

Edit: You can also now get the values of enums with numerical values via the new string number parsing in extends clauses:

 enum ApiMessage {
    logged_ok = 1,
    register_ok = 2,
}

// "logged_ok" | "register_ok"
type ApiMessageTraits = keyof typeof ApiMessage;

// 1 | 2
type MessageContents = `${ApiMessage}` extends `${infer N extends number}` ? N : never;

4 Comments

For me it doesn't work. not sure why. export enum UiActionType { ACTION_MOUSE_UP = 'mouseup', ACTION_MOUSE_DOWN = 'mousedown', ACTION_MOUSE_MOVE = 'mousemove', } type UiActionTypeTraits = keyof typeof UiActionType; type UiActionTypeValues = ${UiActionTypeTraits}; export type SendMouseEventType<T extends UiActionTypeValues> = WindowEventMap[T]
@olNoy The problem was that you were referencing UiActionTypeTraits within UiActionTypeValues instead of UiActionType. Here's a playground link
@sno2 thanks! solved the issue but it's still no good for the other two expressions: typescriptlang.org/play?ts=4.4.0-beta#code/…
@olNoy Ah, the problem is with the way the TypeScript types are setup. Currently, the SendMouseEventType<'mouseup'> is an alias for MouseEvent. WheelEvent does extend MouseEvent and the string passed into MouseEvent is simply for demonstrative purposes and is not type-checked at all with no generics so there is no error. You can check this out by just hovering over this variable name in the playground: type foo2 = SendMouseEventType<'mousedown'>. Hint: it shows MouseEvent.
5

Simplified version

The enum produces an object with key-value pairs.
All you really need to do is find the right entry, if any.

So, given example

enum ApiMessages {
  Ok = 'OK',
  Forbidden = 'No access for you',
}

const example = ApiMessages.Forbidden;

find the enum entry

const match = Object.entries(ApiMessages).find(([key, value]) => value === example);

select the key

if (match) {
  const [key] = match;
  console.log(`'${example}' matched key '${key}'.`);
}

// output: 'No access for you' matched key 'Forbidden'.

that's it.


Find a working example here.

Comments

4

You can use .findKey from lodash here for a one line solution:

import { findKey } from 'lodash';

enum SomeType {
  FOO = 'bar',
  BAZ = 'qux'
}

const example = findKey(SomeType, (o) => o === 'bar');
console.log(example);

Returns 'FOO'

Comments

0

I was sent the enum code from an API so needed a quick way to get the description...

Easy to do in js but not so easy in Typescript!

Uses Soren's method as a base

export function getEnumKeyByEnumValue<T extends { [index: string]: string }>(
  myEnum: T,
  enumValue: string
): keyof T | null {
  let longDesc: string = 'No Match';

  Object.keys(myEnum).forEach((key) => {
    if (key == enumValue) longDesc = myEnum[key];
  });

  return longDesc;
}

getEnumKeyByEnumValue(BenefitType, appeal.benefit)

Comments

0

As an addition to @Søren's answer to go all the way with type safety, you could do something like this:

const getEnumKey = <T extends {}>(theEnum: T, keyValue:T[keyof T]) =>
  (Object.keys(theEnum) as (keyof T)[]).
  find((k ) => theEnum[k] === keyValue) as keyof T || null ;

That way you also get (some) type safety and IDE intellisense ect.: showing types on ide

Comments

-1

Using lodash you could try:

_.keys(ApiMessages)[_.toArray(ApiMessages).indexOf(ApiMessages.logged_ok)]

Should give you back 'logged_ok'

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.