Denys Rybkin
Denys Rybkin

Reputation: 787

How to cast string enum to another enum with the same keys?

Given by these 2 string enums with always the same keys:

enum StatusEnum {
    SUCCESS = 'SUCCESS',
    ERROR = 'ERROR',
    WARN = 'WARN',
    DEBUG = 'DEBUG',
}

enum RawStatusEnum {
    SUCCESS = 'Success',
    ERROR = 'Error',
    WARN = 'Warn',
    DEBUG = 'Debug',
}

I want to cast value of enum RawStatusEnum to value of enum StatusEnum, that is:

const rawEnumValue = RawStatusEnum.ERROR // RawStatusEnum
const normalEnumValue = cast(rawEnumValue) // StatusEnum

But I have problems with typings and ... I've learnt about reverse mapping in typescript here, but reverse mapping works only for numeric enums. Thanks in advance.

Upvotes: 3

Views: 1663

Answers (1)

jcalz
jcalz

Reputation: 328433

As you noted, string enums don't automatically get a reverse mapping, so getting the key from a value isn't as simple as a lookup.

However, it's fairly straightforward to write a helper function to generate a reverse mapping for a string enum. But the compiler can't verify that it's implemented properly so you'll need a type assertion. For example:

const reverseStringEnum = <T extends Record<keyof T, string>>(e: T) =>
  Object.fromEntries(Object.entries(e).map(([k, v]) => [v, k])) as 
   { [K in keyof T as T[K]]: K };

It uses the Object.entries() and Object.fromEntries() methods to split the enum object into key-value pairs and then put it back together with the keys and values swapped. The input object type is T, and the output object type is {[K in keyof T as T[K]]: K}, which uses key remapping to swap the key and value types also.

Let's test it out on RawStatusEnum:

const RawStatusReverseEnum = reverseStringEnum(RawStatusEnum);
/* const RawStatusReverseEnum: {
    readonly Success: "SUCCESS";
    readonly Error: "ERROR";
    readonly Warn: "WARN";
    readonly Debug: "DEBUG";
} */

Looks good. And now we can "cast" a RawStatusEnum to a StatusEnum by doing a pair of lookups. We lookup the key in RawStatusReverseEnum and then use the key to lookup the value in StatusEnum:

const castRawToNormal = <T extends RawStatusEnum>(t: T) =>
  StatusEnum[RawStatusReverseEnum[t]];

Let's use it:

const rawEnumValue = RawStatusEnum.ERROR // RawStatusEnum

const normalEnumValue = castRawToNormal(rawEnumValue);
// const normalEnumValue: StatusEnum.ERROR

console.log(normalEnumValue); // "ERROR"

Looks good. The compiler even knows that normalEnumValue will be of type StatusEnum.ERROR.

Playground link to code

Upvotes: 5

Related Questions