将字符串转换为枚举类型的通用方法

时间:2019-06-25 02:54:50

标签: typescript generics enums

我需要接受用户输入并断言该值是有效的枚举值。例如,我有一个枚举:

enum Gender {
  Male = 'MALE',
  Female = 'FEMALE',
  Neutral = 'NEUTRAL'
}

用户可以在那里输入任何值,如果值是有效的Gender,我想断言该值是Gender

拥有这样的功能会很方便:

function toEnum<T, E extends typeof T>(enumType: E, value: string): T | null {
  if (Object.values(enumType).includes(value)) {
    return value as T;
  }

  return null;
}

我可以这样叫:

const a: Gender = toEnum(Gender, 'FEMALE'); // === Gender.Female
const b: Gender = toEnum(Gender, 'APPLE'); // null;

我真的不知道哪种通用签名最有意义:

function toEnum<T, E extends typeof T>(enumType: E, value: string): T | null { /* ... */ }

function toEnum<T>(enumType: typeof T, value: string): T | null { /* ... */ }

function toEnum<E, T instanceof E>(enumType: E, value: string): T | null { /* ... */ }

我知道那些事情是无效的。我只是想表达我的意图。

无论如何,要使打字稿推断出T型将是一个枚举值,同时又保持类型安全性,最不明智的方法是什么?有没有更好的方法为字符串枚举编写类型安全的反向映射函数?

编辑:

一些其他示例来说明我想实现的目标。我想推断类型T,以便消除奇怪的行为,并避免使用any,以便维护类型安全。

enum Gender {
  Male = 'MALE',
  Female = 'FEMALE',
  Neutral = 'NEUTRAL'
}

type Maybe<T> = T | null;

export function toEnum<T>(enumType: any, value: string): Maybe<T> {
  if (Object.values(enumType).includes(value)) {
    return value as unknown as T;
  }

  return null;
}

const shouldBeFemale: Maybe<Gender> = toEnum<Gender>(Gender, 'FEMALE'); // Gender.Female
const shouldBeNull: Maybe<Gender> = toEnum<Gender>(Gender, 'NOT VALID'); // null

// but this is lame
enum Fruit {
  Apple = 'APPLE',
  Orange = 'ORANGE'
}

const wtfItIsApple: Maybe<Gender> = toEnum<Gender>(Fruit, 'APPLE'); // 'APPLE' . . . wat!?
const validButStillSilly: Maybe<Gender> = toEnum<Gender>(Fruit, 'NOT VALID'); // null
const ughAny: Maybe<Gender> = toEnum<Gender>(217, ':('); // null

1 个答案:

答案 0 :(得分:2)

type IsEnumKey<T, E> = [E] extends [keyof T] ? true : false;
function toEnum<T, E extends (string | keyof T)>(enumType: T, value: E): IsEnumKey<T, E> extends true ? E : E | null {
  if (Object.values(enumType).includes(value)) {
    return value as any;
  }

  return null as any;
}

enum Fruit {
    APPLE = "APPLE",
    ORGANE = "ORANGE",
}

const testType = toEnum(Fruit, "APPLE") // Not or null just "Apple";
const testType1 = toEnum(Fruit, "") // "" | null

让我知道这是否对您有用,这样的条件返回类型是以必须在方法主体中强制转换为代价的(虽然可能避免强制转换或使用类型保护),但要获得对返回类型的额外控制

已知的枚举成员不会将null作为可能的返回类型返回,但是,如果您希望这种情况发生,只需更改返回类型

未知的枚举成员将返回其类型为null的联合。

编辑:这能解决吗?

function toEnum<T, E extends (string | keyof T)>(enumType: T, value: E): [E] extends [keyof T] ? T[E] : E | null {
  if (Object.values(enumType).includes(value)) {
    return value as any;
  }

  return null as any;
}

enum Fruit {
    APPLE = "APPLE",
    ORGANE = "ORANGE",
}

const testType = toEnum(Fruit, "APPLE") // works;
const testType1 = toEnum(Fruit, "") // "" | null
const testType2: Fruit.APPLE = toEnum(Fruit, "APPLE") // works

最终编辑: 您可以使用重载而不是条件返回类型来解决函数主体中的强制转换问题,以下是一种经过修改的更好的解决方案,其中方法主体中不再需要直接强制转换。

function toEnum<T, E extends keyof T>(enumType: T, value: E): T[E];
function toEnum<T, E extends string>(enumType: T, value: E): E | null;
function toEnum<T, E extends string>(enumType: T, value: E): E | null {
  if (Object.values(enumType).includes(value)) {
    return value as E;
  }

  return null;
}