Reverse-Mapping for String Enums

Viewed 38902

I wanted to use string enums in typescript but I can't see a support for reversed mapping in it. I have an enum like this:

enum Mode {
    Silent = "Silent",
    Normal = "Normal",
    Deleted = "Deleted"
}

and I need to use it like this:

let modeStr: string;
let mode: Mode = Mode[modeStr];

and yes I don't know what is it there in modeStr string and I need it parsed to the enum or a fail at parsing in runtime if the string is not presented in the enum definition. How can I do that as neat as it can be? thanks in advance

7 Answers

If you're ok with using Proxies, I made a more generic & compact version:

stringEnum.ts

export type StringEnum<T extends string> = {[K in T]: K}
const proxy = new Proxy({}, {
  get(target, property) {
    return property;
  }
})
export default function stringEnum<T extends string>(): StringEnum<T> {
  return proxy as StringEnum<T>;
}

Usage:

import stringEnum from './stringEnum';
type Mode = "Silent" | "Normal" | "Deleted";
const Mode = stringEnum<Mode>();

None of the answers really worked for me, so I have to fall back to normal for the loop. my enum is

enum SOME_CONST{
      STRING1='value1', 
      STRING2 ='value2',
      .....and so on
}


getKeyFromValue =(value:string)=> Object.entries(SOME_CONST).filter((item)=>item[1]===value)[0][0];

This answer is from @PhiLho,

I dont have enough rep to comment on his message.

let reverseMode = new Map<string, Mode>();
Object.keys(Mode).forEach((mode: Mode) => {
    const modeValue: string = Mode[<any>mode];
    reverseMode.set(modeValue, mode);
});

However, the first argument for .set should be the key, and the second should be the value.

reverseMode.set(modeValue, mode);

should be...

reverseMode.set(mode, modeValue);

Resulting in this...

let reverseMode = new Map<string, ErrorMessage>();
Object.keys(ErrorMessage).forEach((mode: ErrorMessage) => {
    const modeValue: string = ErrorMessage[<any>mode];
    reverseMode.set(mode, modeValue);
});

The reason why @PhiLho might have missed this is because the keys and values for the original object provided by OP are the same.

The standard types achieves this function - just check for undefined and handle however.

type Mode = "Silent" | "Normal" | "Deleted";
const a = Mode["something else"];
//a will be undefined

Also, you can add on a record type to the above to extend the "enum", say to translate a short enum key into more verbose output (https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type):

const VerboseMode: Record<Mode, string> = {
  Silent: "Silent mode",
  Normal: "Normal mode",
  Deleted: "This thing has been deleted"
}

For all of you still struggling with this, this is a fast and dirty way of getting this done.

enum Mode {
    Silent = "Silent",
    Normal = "Normal",
    Deleted = "Deleted"
}

const currMode = 'Silent',
  modeKey = Object.keys(Mode)[(Object.values(Mode) as string[]).indexOf(currMode)];

However, there are some caveats, that you should be aware of!

The most likely reason Microsoft marked the Git bug as "working as intended", is probably due to the fact that this deals with String Literals where as enums were originally designed to be indexable. While the above code works and lints with TSLint, it has two issues. Objects are not guaranteed to retain the intended order when converted to an Array and we are dealing with String Literals which may or may not be unique.

Take a look at this StackBlitz where there are two Mode's with values of 'Silent'. You have zero guarantee that it won't return 'Ignore' over 'Silent' once the lookup completes.

https://stackblitz.com/edit/typescript-hyndj1?file=index.ts

Related