简体   繁体   中英

Using `enum` in ternary operator TypeScript

I am trying to set an object with a ternary operator based on a config value and an enum

import { config } from 'src/config'
import {logLevelEnum} from 'a-package-installed'

const someObject = {
  logLevel: config.logLevel ? logLevelEnum[config.logLevel] : logLevelEnum.NOTHING,
}

The enum is basically this:

export enum logLevelEnum {
  NOTHING = 0,
  ERROR = 1,
  WARN = 2,
  INFO = 4,
  DEBUG = 5,
}

But I get the compilation error:

Element implicitly has an 'any' type because index expression is not of type 'number'.
logLevel: config.logLevel ? logLevelEnum[config.logLevel] : logLevelEnum.NOTHING,
                                         ~~~~~~~~~~~~~~~

But I don't understand why it says that the index expression is supposed to be number since is an enum .

Can someone explain to me why and how can I achieve what I need?

Much appreciated.

The problem is that config.logLevel is of type string while there is actually only a subset of valid strings.

So declare config.logLevel as a union type: 'NOTHING' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' 'NOTHING' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'

This union type doesn't seem to be generatable from the enum according to this: Generic type to get enum keys as union string in typescript?

Related Typescript Playground Example

Some basics

You can access your enum in different ways:

  1. logLevelEnum.WARN = 2 : by the enum directly
  2. logLevelEnum['WARN'] = 2 : via index operator
  3. logLevelEnum[2] = WARN : get the enum-name from an enum-value
    This works because typescript creates a reverse mapping

When you try to access an invalid enum, you get undefined at runtime. Typescript tries to avoid this situation and gives you a compile error when possible to avoid this:

  1. logLevelEnum.warning : Property 'warning' does not exist on type 'typeof logLevelEnum'.
  2. logLevelEnum['warning'] = undefined : Element implicitly has an 'any' type because index expression is not of type 'number'.
    • this is maybe a little confusing, as it seems to indicate that you can only use number as index - which is not true - see 2. above
    • but the basic statement is right: typescript cannot guarantee that this expression returns a valid enum, thus the type of this expression is any (and not logLevelEnum or logLevelEnum | undefined , etc.)
      Hint: you can see the type when you hover over the invalidStringIndex variable in the Typescript Playground Example
  3. logLevelEnum[999] = undefined :
    • unfortunately we don't get a compile error here, but it is obviously not a valid index
    • the type of this expression is string ! But actually it can also be undefined .
      Hint: when you activate the typescript-compiler option noUncheckedIndexedAccess , then the type will be string|undefined which is more accurate

Answer to your question

As I understand your question

  • config.logLevel is of type string and not under your control.
    If it were under your control, the type should be logLevelEnum and everything would be easier: logLevelEnum[logLevelEnum] is then guaranteed to be a valid enum (at compile time)
  • you want to get a valid log-level: when config.logLevel is valid, you want to use it, otherwise you want to use logLevelEnum.NOTHING

So basically you need a function like this (which you call with config.logLevel ):

function getValidLogLevelEnum(logLevelName: string): logLevelEnum {
  /**
   * we need the correct type so that typescript will allow the index access
   * note: we must cast the string to `keyof typeof logLevelEnum`
   *       which resolves to: "NOTHING" | "ERROR" | "WARN" | "INFO" | "DEBUG"
   *       i.e. all valid enum-names
   */
  const enumName = logLevelName as keyof typeof logLevelEnum;
  /**
   * now that we have the correct type of the enum-name, we can get the enum-value
   */
  const configEnum = logLevelEnum[enumName];
  /**
   * keep in mind, that we were cheating a little bit in the type-expression above
   * We told typesript that we are sure that enumName is a valid enum-name,
   * but actually it is just the value of the logLevelName string, which could be anything.
   * Thus, typescript now thinks that configEnum is of type logLevelEnum, but 
   * actually it is `logLevelEnum | undefined` (undefined when logLevelEnum is not a valid enum-name)
   * 
   * This is the reason why use the nullish coalescing operator (??):
   * see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing
   * 
   * so we return configEnum, or logLevelEnum.NOTHING (when configEnum is undefined) 
   */
  return configEnum ?? logLevelEnum.NOTHING;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM