2

I have an interface with

interface mathTest {
   mathAction: MathActionEnum;
}

The reason for this is that I want this property to have just one of the specific values from the enum below.

enum MathActionEnum {
    'byOne' = 1,
    'byTwo' = 2,
    'byFour' = 4,
    'byEight' = 8,
}

Assume mathAction = 'byOne' -> received from an API response.

First scenario: doing an arithmetic operation, I need the number value: let result: number = amount / MathActionEnum[mathAction] but I get an error:

The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type

It is a number but still I need to cast it with Number(MathActionEnum[mathAction]) for the error to go away.

Second scenario: equality check, I need the string value: if (mathAction === MathActionEnum[MathActionEnum.byOne]) but I get an error:

This condition will always return 'false' since the types 'MathActionEnum' and 'string' have no overlap

Which makes sense.

I'm a bit lost, is there a way to syntax it as I expect it to be? Maybe I need to define things differently? Thanks

20
  • Doesn't MathThingEnum[MathThingEnum.byOne] return a number? Commented Nov 7, 2022 at 21:14
  • MathThingEnum.byOne return a number -> 1, MathThingEnum[MathThingEnum.byOne] returns a string -> 'byOne' Commented Nov 7, 2022 at 21:15
  • 1
    Oops, my bad. str in my case is defined as MathThingEnum but it actually contains a string value. I will rephrase my question then. Thanks. Commented Nov 7, 2022 at 21:18
  • MathActionEnum[mathAction] is a string Commented Nov 7, 2022 at 21:51
  • It is, I wrote the question differently. Commented Nov 7, 2022 at 21:52

2 Answers 2

3

TypeScript enums are absolutely NOT suitable for any sort of key-value mapping. The intent is to have a grouping of uniquely identifiable labels, but labels are where it ends. While they may indeed have a number representation under the hood, they are not intended for use as a key-value store. You will have to cast it to "extract the number", and then the type is just number, so you effectively defeat the purpose of enums.

For all intents and purposes, think of them like keys with no useful values:

const MathActionEnum = Object.freeze({
  byOne: Symbol(),
  byTwo: Symbol(),
  byFour: Symbol(),
  byEight: Symbol(),
})

Consider the newer alternative, const assertion, instead. They'll provide you with type safety on both keys and values:

const MathActions = {
  'byOne': 1,
  'byTwo': 2,
  'byFour': 4,
  'byEight': 8,
} as const

type MathAction = keyof typeof MathActions

type MathActionValue = typeof MathActions[MathAction]

You get full type safety on both keys and values:

const example = (action: MathAction) => {
    return 2 * MathActions[action]
}

example('byOne')

// compile error, not a valid key
example('foo')

 // -------------
const example2 = (actionValue: MathActionValue) => {
    return 2 * actionValue
}

example2(4)

// compile error, not a valid value
example2(19)

You can even add type assertions to check if arbitrary values are a key or value:

const isAction = (action: string): action is MathAction => {
    return Object.keys(MathActions).includes(action)
}
isAction

const isActionValue = (actionValue: number): actionValue is MathActionValue => {
    return Object.values(MathActions).includes(actionValue as any)
}

You'll even get IDE autocompletion for both keys and values:

enter image description here

Here's a Playground

Sign up to request clarification or add additional context in comments.

7 Comments

I understand your approach but assuming I achieved what I need using enums, how this is different/better than using enums?
I'm not sure I follow. This is statically type-safe. There's no way to use enums for this that is. You'll have to use a type assertion somewhere, which is means the type checker can't help you prevent errors. BTW Number(value) is NOT a cast, it's a parse. typescriptlang.org/play?#code/…
Sorry if I was unclear with my question in my previous comment: Eventually I managed to achieve what I want using an enum (see my comments with Konrad above) so I wonder what advantage I will have with the const assertion.
You didn't achieve it in a typesafe manner. Konrad's comment about as is true. It's just lying to the compiler. But then his next comment uses as. Here you get no type errors, just an unexpected NaN: typescriptlang.org/play?#code/…
I will try this later today and update here, thanks!
|
0

Typescript does not expose the right enum type (solved this by workaround) It simply filters non-numeric values out.

Object.values(yourTsEnum).filter((item : any) => /^([0-9]*)$/i.test(String(item))).map(item => Number(item))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.