Bogdan Surai
Bogdan Surai

Reputation: 1275

Transform one enum to another enum

Is there a way for transforming enums in Typescript? For example. I have an original enum

enum original {
  VALUE_1
  VALUE_2
}

I'd like to transform uppercase to lowercase, as

enum original {
  value_1
  value_2
}

Is it possible?

Upvotes: 0

Views: 1154

Answers (1)

jcalz
jcalz

Reputation: 328433

In general, you can use write a function called lowercaseKeys() to transform the keys of any object to lowercase, and then assert that such a function returns a strongly typed result. The return type involves key remapping to make a mapped type where the string literal keys into their lowercase counterparts via the Lowercase intrinsic string manipulation type:

function lowercaseKeys<T extends object>(obj: T): { 
  [K in keyof T as K extends string ? Lowercase<K> : K]: T[K] 
} {
    return Object.fromEntries(
      Object.entries(obj).map(([k, v]) => [k.toLowerCase(), v])
    ) as any;
}

Again, this should work for any object:

const example = lowercaseKeys({ STR: "hello", NUM: Math.PI });
/* const example: {
    str: string;
    num: number;
} */
console.log(example.num.toFixed(2)); // 3.14

You can see that at runtime, example has keys named str and num, and the compiler is aware of this.


An enum is also an object with keys and values, so you can transform it the same way:

const LowercaseEnum = lowercaseKeys(OriginalEnum);
/* const LowercaseEnum: {
    [x: number]: string;
    readonly value_1: OriginalEnum.VALUE_1;
    readonly value_2: OriginalEnum.VALUE_2;
} */
console.log(LowercaseEnum.value_1.toFixed()); // "0"
console.log(LowercaseEnum.value_2.toFixed()); // "1"

Hooray!


Well, that's great as far as it goes. But note that an enum also has some special features apart from a regular object. (See this answer for a more in-depth description of this differences.) For example, the name of an enum can also be used as a type corresponding to the union of its values:

interface Okay {
    e: OriginalEnum;
}

But LowercaseEnum is just an object and was not declared as an enum; there is no type named LowercaseEnum:

interface Oops {
    e: LowercaseEnum; // error
    // ~~~~~~~~~~~~~
    // 'LowercaseEnum' refers to a value, 
    // but is being used as a type here.
}

If you want such a type, you must make it:

type LowercaseEnum = OriginalEnum; // same values, so same type

Another difference is that a true numeric enum like OriginalEnum has a reverse mapping, where if OriginalEnum.VALUE_1 is 0, then OriginalEnum[0] is "VALUE_1":

console.log(OriginalEnum[0]); // "VALUE_1" as expected

But the transformed version doesn't have a useful reverse mapping, because only its keys have been changed; its values are untouched:

console.log(LowercaseEnum[0]); // still "VALUE_1", unexpected?

So, please be careful when using something like LowercaseEnum: while there are similarities, it's not a true enum.


Playground link to code

Upvotes: 2

Related Questions