3

Although not necessary, enum keys are usually declared all in CAPS. However, the enum value can be different from the key itself.

declare enum MyEnum {
    PRODUCTION = "production",
    DEVELOPMENT = "development",
    SANDBOX = "sandbox"
}

If I were to use an external string value matching my enum value to index my enum, It will not work as the key is not the same as the value:

const lowercase = "production";
const UPPERCASE = "PRODUCTION";

const bad = MyEnum[lowercase]; // does not work
const good = MyEnum[UPPERCASE]; // works

I want to use the enum value as a way to index the enum. Am I forced to always match identically the enum value and enum key to be able to index the enum dynamically, or do I have to use some string value manipulation to ensure the key matches the value?

bombillazo
  • 296
  • 2
  • 7
  • 14
  • 1
    Does this answer your question? [How can I cast a string to an enum in Typescript](https://stackoverflow.com/questions/62821682/how-can-i-cast-a-string-to-an-enum-in-typescript) – Blackhole Sep 04 '22 at 18:15
  • Why are you using enums here at all? What is the intent of writing `MyEnum["production"]` if the goal is to get `"production"`? Why not just use `"production"` itself rather than passing it through another object? I am failing to understand the use case. – jcalz Sep 04 '22 at 18:25
  • Like, let's remove `enum` from the situation entirely by replacing it with a `const`-asserted object as shown [here](https://tsplay.dev/wQxjGW). You have the same issue, where you can't just index into it with something that isn't a key. Do you agree that this is the same issue? If so, then we can dispense with `enum` and just talk about how to index into any object with a case-insensitive key – jcalz Sep 04 '22 at 18:28
  • If so, then you should just convert to uppercase before indexing, possibly with a helper function to tell the compiler that the literal type should also be uppercase, like [this](https://tsplay.dev/NDRa6W). If you need an enum then like [this](https://tsplay.dev/N5La0w). Does this fully address your question? If so I can write up an answer explaining. If not, what am I missing? (Please @jcalz mention me if you reply, or I won't be notified) – jcalz Sep 04 '22 at 18:31
  • @Blackhole hey, not really. I am not trying to cast the values to an enum. – bombillazo Sep 05 '22 at 04:24
  • @jcalz This example is a simplification to avoid going on tangents but essentially what I am trying to do is to dynamically map one enum and its values (which I don't control and does not necessarily follow the ALL CAPS convention) to my own enum which all have the ALL CAPS convention. Some of the enums I consume use pascal case, others are all caps and some of their enum keys don't match the value. On my side, all key-values match and are ALL CAPS. Also, I am trying to avoid casting from the external enum value to mine. – bombillazo Sep 05 '22 at 04:32
  • Sorry, I'm confused. Is there anything specific about [this proposed solution](https://tsplay.dev/mbn2dm) that doesn't work for you? If not, I can write up an answer. If so, what specifically doesn't work (ideally you'd [edit] the code in your example to demonstrate the failing use case). – jcalz Sep 05 '22 at 14:18
  • @jcalz Yes that is a valid solution, it would be the solution I envisioned when I stated `or do I have to use some string value manipulation to ensure the key matches the value`. If this is the only way, I can mark that as the answer. I was trying to avoid adding an additional operation to the enum mapping. – bombillazo Sep 05 '22 at 16:53

2 Answers2

4

Enums in TypeScript emit as plain JavaScript objects. Thus to some extent this question is really about how you can access JavaScript objects with case-insensitive keys. The short answer is that you can't and you need to work around it. The longer answer is that you could probably build a Proxy for your object that behaves the way you want, but it would be a lot more complicated than just working around it, especially anything that would convince TypeScript that this is what you're doing.


If you know that the object keys are all uppercased then the easiest workaround is to just uppercase the candidate key yourself before indexing into the object. TypeScript doesn't understand what the toUpperCase() method of strings does to string literal types. For example, if you have a value v of type "foo", the compiler won't understand that v.toUpperCase() is a value of type "FOO":

let x: "FOO" = "foo".toUpperCase(); // error!

but you could write a wrapper for the toUpperCase() method that behaves this way:

const uc = <T extends string>(x: T) => x.toUpperCase() as Uppercase<T>;

let x: "FOO" = uc("foo"); // okay

Here uc is using the Uppercase<T> intrinsic type in its return type.

So the workaround here is to just pass keys through uc() before indexing:

const UPPERCASE = "PRODUCTION";
const good = MyEnum[uc(UPPERCASE)]; // okay
// const good: MyEnum.PRODUCTION

const lowercase = "production";
const stillGood = MyEnum[uc(lowercase)]; // okay
// const stillGood: MyEnum.PRODUCTION

If you don't know that the keys are all uppercase then you need to search your object for an appropriate key. You can write a helper function that does this, and even give it a call signature that TypeScript can use:

function idxIgnoreCase<K extends string, T extends object>(
    obj: T,
    k: K extends (
        Uppercase<K> extends Uppercase<Extract<keyof T, string>> ? unknown : never
    ) ? K : Extract<keyof T, string>
): { [P in Extract<keyof T, string>]-?:
    Uppercase<P> extends Uppercase<K> ? T[P] : never
}[Extract<keyof T, string>] {
    return Object.entries(obj).find(
        ([p, v]) => k.toUpperCase() === p.toUpperCase())?.[1] as any;
}

That's ugly, but it works:

const ret = idxIgnoreCase(MyEnum, "dEvElOpMeNt");
// const ret: MyEnum.DEVELOPMENT
console.log(ret) // "development"

I'm not even going to try to write the helper function as a Proxy, since doing so would require telling TypeScript that the resulting object has hundreds of keys, one for each possible case for the original keys. And all for the dubious advantage of writing MyProxyEnum.dEvElOpMeNt instead of f(MyEnum, "dEvElOpMeNt"). Especially since other TypeScript and JavaScript developers looking at the code might be very confused, since they'd be expecting case sensitive keys.

Again, the workaround of modifying the key before indexing is by far the easiest and most understandable approach.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
0

This is enum naming convention:

Use uppercase letters for your enums - they are global constants which have usable values. They are more than just types, which use usually like Foo or TFoo.

The keys on an enum tend to be titlecase.

However, in this case, there is no need to use Enums

Arman Ashoori
  • 265
  • 1
  • 8