35
votes

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

6
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 numberStramski
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!Tadhg McDonald-Jensen

6 Answers

65
votes

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
    };
}
9
votes

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'
7
votes

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.

3
votes

For those who come looking as I did, this works for me.

NOTE: This is only valid if you know that your string value is a valid 'key'.

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

const str: string = 'apple';

const fruit = str as Fruit;

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

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

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.

-1
votes

Using lodash you could try:

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

Should give you back 'logged_ok'