Home > Enterprise >  Typescript : Checking enum values at runtime for enum with associated methods
Typescript : Checking enum values at runtime for enum with associated methods

Time:02-04

In order to enhance code reliability, I cast raw strings to enum values using this function:

export function getSafelyEnumValue<T>(value: string | undefined, enumClass: { [s: string]: string }): T {
    if (value === undefined) {
        throw new NullPointerException()
    }
    if (!Object.values(enumClass).includes(value)) {
        throw new UnknownEnumRawValueException(value)
    }
    return value as T
}

Then, when I want to convert a string to an enum type:

const enumValue = getSafelyEnumValue<MyEnum>(rawString, MyEnum)

This is better than doing a simple cast :

const enumValue = rawString as MyEnum 

Because this last code does not throw any exception if rawString does not belong to MyEnum, while getSafelyEnumValue does. This function works very well when I have simple enums like this :

export enum OrderSideDto {
    SELL = "sell",
    BUY = "buy",
} 

Sometimes though, I add methods to enums, to convert them back and forth to other enum types. For this, I use the well known pattern that creates a namespace with the same name as the enum, and put the methods inside it:

export namespace OrderSideDto {
    export function toOrderSide(orderSideDto: OrderSideDto): OrderSideEntity {
        switch(orderSideDto) {
            case OrderSideDto.SELL: return OrderSideEntity.SHORT;
            case OrderSideDto.BUY: return OrderSideEntity.LONG;
        }
    }
}

When I use this pattern, my method getSafelyEnumValue doesn't compile anymore, I have this error from TS :

Argument of type 'typeof OrderSideDto' is not assignable to parameter of type '{ [s: string]: string; }'.   Property 'toOrderSide' is incompatible with index signature.     Type '(orderSideDto: OrderSideDto) => Side' is not assignable to type 'string'

Any idea on how to make getSafelyEnumValue compatible with enum with methods ?

CodePudding user response:

An enum is converted to a POJO. Take this as an example:

This enum in typescript...

enum OrderSideDto {
    SELL = "sell",
    BUY = "buy",
}

... is converted into the following in javascript (please, note that number-based enums have an slightly different conversion):

var OrderSideDto;
(function (OrderSideDto) {
    OrderSideDto["SELL"] = "sell";
    OrderSideDto["BUY"] = "buy";
})(OrderSideDto || (OrderSideDto = {}));

This convoluted code, when executed, results in this (again, number-based enums are a bit different):

{
  "SELL": "sell",
  "BUY": "buy"
}

So far so good. Now, when you declare a namespace, what happens is that either JS creates an object with this name or it takes an existing one with this name. In your case, the later happens. Either way, the contents of the declared namespace are stuffed into the requested object.

This means that, in your case, your object is stuffed with the function declared in the namespace (not exactly as I depicted it in here but you get the point):

{
  "SELL": "sell",
  "BUY": "buy",
  "toOrderSide": (/* ... */) => { /* ... */ }
}

In other words, you end up with an object with the properties of the enum and the ones in the namespace smashed together.

However, none of this was your issue. The problem you had is that you where expecting enumClass to be an object of any sort whose properties are composed strictly of strings only:

{ [s: string]: string }
  ^^^^^^^^^^^  ^^^^^^
       |       who's value is an string only
       |
  any property declarable with an string

So, you just needed to also accept a callable (i.e. Function).

  • Related