TypeScript枚举转换为对象数组

247

我有一个这样定义的枚举:

export enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}

但我希望它以以下方式从我们的API表示为对象数组/列表:

[{id: 1, name: 'Percentage'}, 
 {id: 2, name: 'Numeric Target'},
 {id: 3, name: 'Completed Tasks'},
 {id: 4, name: 'Average Milestone Progress'},
 {id: 5, name: 'Not Measured'}]

有没有一种简单且本地化的方法来实现这个问题,或者我必须构建一个函数,将枚举类型转换为int和字符串,并将对象构建成数组?

有没有一种简单且本地化的方法来实现这个问题,或者我必须构建一个函数,将枚举类型转换为int和字符串,并将对象构建成数组?


枚举是在运行时存在的真实对象。因此,您可以通过执行以下操作来反转映射:GoalProgressMeasurements[GoalProgressMeasurements.Completed_Tasks] 以获取枚举名称。我不知道这是否有帮助。 - Diullei
你能更好地描述一下“来自我们的API”吗?可以举个使用的例子吗? - gilamran
31个回答

157

如果您正在使用ES8

仅针对这种情况,它将完美地工作。它将为您提供给定枚举的值数组。

enum Colors {
  WHITE = 0,
  BLACK = 1,
  BLUE = 3
}

const colorValueArray = Object.values(Colors); //[ 'WHITE', 'BLACK', 'BLUE', 0, 1, 3 ]

你会得到这样的 colorValueArray ['WHITE','BLACK','BLUE',0,1,3] 。 所有键都将位于数组的前半部分,所有值都在后半部分。
即使是这种枚举类型也可以正常工作。
enum Operation {
    READ,
    WRITE,
    EXECUTE
}

但是这种解决方案对于像异构枚举这样的情况不起作用。

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

18
请记住,这将产生重复项。每个元素的字符串值和数字值是一种类型(string | YourEnumType)[],这可能并不是在每种情况下你想要的。 - Christian Ivicevic
7
第一半部分是键,第二半部分是值,这个保证吗?有参考资料吗? - Neekey
@zedd45 你具体指的是什么?该链接基本上是指向846页的标准文档,因此很难理解你认为哪个方面有问题。 - bluenote10
我已经删除了我的评论,因为我不再记得去年八月份它原本是要解决什么问题。我想也许有一些关于EcmaScript命名规则本身的评论,与TS Enums没有直接关系。 - zedd45
在 TypeScript v5 中,情况已经不再是这样了。 - Dorklord

97

一个棘手的问题是,TypeScript将在生成的对象中“双重”映射枚举,因此可以通过键和值进行访问。

enum MyEnum {
    Part1 = 0,
    Part2 = 1
}

将被发出为

{
   Part1: 0,
   Part2: 1,
   0: 'Part1',
   1: 'Part2'
}

因此,在映射之前,您应该先过滤对象。所以@Diullei的解决方案是正确的答案。这是我的实现:

// Helper
const StringIsNumber = value => isNaN(Number(value)) === false;

// Turn enum into array
function ToArray(enumme) {
    return Object.keys(enumme)
        .filter(StringIsNumber)
        .map(key => enumme[key]);
}

使用方法如下:

export enum GoalProgressMeasurements {
    Percentage,
    Numeric_Target,
    Completed_Tasks,
    Average_Milestone_Progress,
    Not_Measured
}

console.log(ToArray(GoalProgressMeasurements));

2
如果 enum MyEnum { Part1 = 0, Part2 = 1 } 转换成{ Part1: 0, Part2: 1, 0: 'Part1', 1: 'Part2' } 那么,为什么当你 console.log(Object.values(MyEnum)) 时只会打印出 0,1 呢? - Juan José Ramírez
1
@JuanJoséRamírez,你在哪里看到的?对我来说,Object.values(MyEnum) 的计算结果是 ["Part1", "Part2", 0, 1] - user8363
我刚刚在我的组件中打印了 console.log(Object.values(MyEnum))。我正在使用 Angular,并不确定是否相关。我对 TypeScript 不是很有经验。 - Juan José Ramírez
不同的TS版本是否会改变行为? - Juan José Ramírez
10
我已经查看了文档 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-4.html 发现字符串枚举有不同的行为。它们根本不会生成反向映射。在我的代码中,我正在使用字符串枚举,而不是此示例中的字符串。 - Juan José Ramírez
1
这可能会失败,例如像 '1e5' = 1 这样的成员,但这是非常特殊的情况。 - TrueWill

48

枚举是在运行时真正存在的对象。因此,您可以执行类似以下方式的反向映射:

let value = GoalProgressMeasurements.Not_Measured;
console.log(GoalProgressMeasurements[value]);
// => Not_Measured
根据此,您可以使用以下代码:

Based on that you can use the following code:

export enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}

let map: {id: number; name: string}[] = [];

for(var n in GoalProgressMeasurements) {
    if (typeof GoalProgressMeasurements[n] === 'number') {
        map.push({id: <any>GoalProgressMeasurements[n], name: n});
    }
}

console.log(map);

参考: https://www.typescriptlang.org/docs/handbook/enums.html


3
除非到“=5”这里,你不需要写出默认值“=2”-在“=1”之后的任何内容都会自动+1。 - sebilasse
14
也许你并不需要这样做,但它会更有表现力,这在我看来会更好。 - SubliemeSiem
5
注意,这对于字符串值枚举无效。 - Bashar Ali Labadi

48

简单来说,这将返回一个枚举值数组:

 Object.values(myEnum);

因为它没有给出正确的结果,请查看 https://dev59.com/dFgQ5IYBdhLWcg3wORZJ#57266281。 - walox
@walox,它只会给出正确的结果,你提供的链接没有显示正确的值。理想情况下,object.keys将返回键的数组,而object.values将返回值的数组。 - Gaurav Panwar
3
如果您的枚举由键-字符串对组成,例如 enum xxx {create = "create"},那么这就足够了。 - nonNumericalFloat

35

简单的解决方案。您可以使用以下函数将枚举转换为对象数组。

 buildGoalProgressMeasurementsArray(): Object[] {

    return Object.keys(GoalProgressMeasurements)
              .map(key => ({ id: GoalProgressMeasurements[key], name: key }))
 }
如果您需要去掉下划线,我们可以使用以下正则表达式:

如果你需要去掉那个下划线,我们可以使用正则表达式如下:


buildGoalProgressMeasurementsArray(): Object[] {

    return Object.keys(GoalProgressMeasurements)
              .map(key => ({ id: GoalProgressMeasurements[key], name: key.replace(/_/g, ' ') }))
 }

11
你需要筛选数值类型的键:Object.keys(GoalProgressMeasurements) .filter(key => typeof GoalProgressMeasurements[key] === 'number') .map(key => ({ id: GoalProgressMeasurements[key], name: key })) - Salvador Rubio Martinez
适用于基于字符串的枚举类型,如下所示: export enum UserRole { STUDENT = '学生', DIRECTOR = '专业主任', AUTHORITY = '权威人士', FINANCIAL = '财务主管' } - Juan Rojas

23
我使用。
Object.entries(GoalProgressMeasurement).filter(e => !isNaN(e[0]as any)).map(e => ({ name: e[1], id: e[0] }));

一个简单的1行代码就能完成任务。

它只需要3个简单的步骤就能完成任务:
- 使用Object.entries加载键值对的组合。
- 过滤掉非数字(因为TypeScript生成反向查找的值)。
- 然后将其映射为我们喜欢的数组对象。


不支持IE前端(虽然不支持IE是一个很好的答案...但我猜客户们不会喜欢)。希望Babel可以转换它,但由于我还没有验证,所以坚持使用其他方法。 - TamusJRoyce
字符串枚举很简单,只需要执行 Object.values(GoalProgressMeasurement) - CMS

18

感谢polkovnikov.ph,我终于找到了一个解决大多数使用情况的有效方法。

问题的有效解决方案

type Descripted<T> = {
    [K in keyof T]: {
        readonly id: T[K];
        readonly description: string;
    }
}[keyof T]

/**
 * Helper to produce an array of enum descriptors.
 * @param enumeration Enumeration object.
 * @param separatorRegex Regex that would catch the separator in your enum key.
 */
export function enumToDescriptedArray<T>(enumeration: T, separatorRegex: RegExp = /_/g): Descripted<T>[] {
    return (Object.keys(enumeration) as Array<keyof T>)
        .filter(key => isNaN(Number(key)))
        .filter(key => typeof enumeration[key] === "number" || typeof enumeration[key] === "string")
        .map(key => ({
            id: enumeration[key],
            description: String(key).replace(separatorRegex, ' '),
        }));
}

例子:


export enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}

console.log(enumToDescriptedArray(GoalProgressMeasurements))
// Produces:
/*
[
    {id: 1, description: "Percentage"},
    {id: 2, description: "Numeric Target"},
    {id: 3, description: "Completed Tasks"},
    {id: 4, description: "Average Milestone Progress"},
    {id: 5, description: "Not Measured"}
]
*/

另外,我使用的一种有用的实用函数是将枚举对象映射到其可用值数组的方法:

映射器

type NonFunctional<T> = T extends Function ? never : T;

/**
 * Helper to produce an array of enum values.
 * @param enumeration Enumeration object.
 */
export function enumToArray<T>(enumeration: T): NonFunctional<T[keyof T]>[] {
    return Object.keys(enumeration)
        .filter(key => isNaN(Number(key)))
        .map(key => enumeration[key])
        .filter(val => typeof val === "number" || typeof val === "string");
}

工作用例

  • 数字枚举
enum Colors1 {
    WHITE = 0,
    BLACK = 1
}
console.log(Object.values(Colors1)); // ['WHITE', 'BLACK', 0, 1]
console.log(enumToArray(Colors1));   // [0, 1]
  • 字符串枚举
enum Colors2 {
    WHITE = "white",
    BLACK = "black"
}
console.log(Object.values(Colors2)); // ['white', 'black']
console.log(enumToArray(Colors2));   // ['white', 'black']
  • 异构枚举
enum Colors4 {
    WHITE = "white",
    BLACK = 0
}
console.log(Object.values(Colors4)); // ["BLACK", "white", 0]
console.log(enumToArray(Colors4));   // ["white", 0]
  • 枚举与导出函数的命名空间合并

enum Colors3 {
    WHITE = "white",
    BLACK = "black"
}
namespace Colors3 {
    export function fun() {}
}
console.log(Object.values(Colors3)); // ['white', 'black', Function]
console.log(enumToArray(Colors3));   // ['white', 'black']

Object.values 返回的属性和 for..in 循环的顺序一致,而 for..in 则按任意顺序返回它们。这段代码可能会根据平台返回一些任意的键和值集合。 - polkovnikov.ph
@polkovnikov.ph,你说得对,谢谢!现在新的实现不依赖于Object.values的顺序。 - Viktor Chernodub
如果有数字值,它也可以是异构枚举(请参阅文档),而此实现将丢失“字符串”值。此外,它仍然没有回答原始问题。 - polkovnikov.ph
谢谢,我尝试改进了答案,并为问题添加了确切的解决方案。 - Viktor Chernodub

17
class EnumHelpers {

    static getNamesAndValues<T extends number>(e: any) {
        return EnumHelpers.getNames(e).map(n => ({ name: n, value: e[n] as T }));
    }

    static getNames(e: any) {
        return EnumHelpers.getObjValues(e).filter(v => typeof v === 'string') as string[];
    }

    static getValues<T extends number>(e: any) {
        return EnumHelpers.getObjValues(e).filter(v => typeof v === 'number') as T[];
    }

    static getSelectList<T extends number, U>(e: any, stringConverter: (arg: U) => string) {
        const selectList = new Map<T, string>();
        this.getValues(e).forEach(val => selectList.set(val as T, stringConverter(val as unknown as U)));
        return selectList;
    }

    static getSelectListAsArray<T extends number, U>(e: any, stringConverter: (arg: U) => string) {
        return Array.from(this.getSelectList(e, stringConverter), value => ({ value: value[0] as T, presentation: value[1] }));
    }

    private static getObjValues(e: any): (number | string)[] {
        return Object.keys(e).map(k => e[k]);
    }
}

1
感谢这些帮手。非常有用。 - user906573
谢谢这个。省去了我几个小时的折腾。真的很有用,可以重复使用。 - GrahamJRoy
失败了!并非所有的枚举都有整数值。 - Scott P.

12

我不喜欢上述任何答案,因为它们都没有正确处理TypeScript枚举中可作为值的字符串/数字混合。

以下函数遵循TypeScript枚举的语义,以便正确地将键映射到值。从那里,获得对象数组或仅键或仅值的数组都很容易。

/**
 * Converts the given enum to a map of the keys to the values.
 * @param enumeration The enum to convert to a map.
 */
function enumToMap(enumeration: any): Map<string, string | number> {
  const map = new Map<string, string | number>();
  for (let key in enumeration) {
      //TypeScript does not allow enum keys to be numeric
      if (!isNaN(Number(key))) continue;

      const val = enumeration[key] as string | number;

      //TypeScript does not allow enum value to be null or undefined
      if (val !== undefined && val !== null)
          map.set(key, val);
  }

  return map;
}

例子用法:

enum Dog {
    Rover = 1,
    Lassie = "Collie",
    Fido = 3,
    Cody = "Mutt",
}

let map = enumToMap(Dog); //Map of keys to values

let objs = Array.from(map.entries()).map(m => ({id: m[1], name: m[0]})); //Objects as asked for in OP
let entries = Array.from(map.entries()); //Array of each entry
let keys = Array.from(map.keys()); //An array of keys
let values = Array.from(map.values()); //An array of values

我还要指出,原帖中对于枚举类型的理解是反过来的。在枚举类型中,“键”实际上在左侧,而“值”在右侧。TypeScript允许您在右侧任意重复值。


测试结果为 TS4.6,objs: [ { id: 1, name: 'Rover' }, { id: 'Collie', name: 'Lassie' }, { id: 3, name: 'Fido' }, { id: 'Mutt', name: 'Cody' } ]entries: [ [ 'Rover', 1 ], [ 'Lassie', 'Collie' ], [ 'Fido', 3 ], [ 'Cody', 'Mutt' ] ]keys: [ 'Rover', 'Lassie', 'Fido', 'Cody' ] values: [ 1, 'Collie', 3, 'Mutt' ] - Pablo LION

8

在数组中获取枚举值的示例:

export enum DocumentationTypeEnum {
  GDPR = 'GDPR',
  HELP = 'HELP',
  OTHER = 'OTHER',
  FOOTER = 'FOOTER'
}
const keys = Object.keys(DocumentationTypeEnum);
    
console.log(keys); // Output :  ["GDPR", "HELP", "OTHER", "FOOTER"]

我该如何将其转换为有效类型?因此,不是 Array<string>,而是一个只能容纳枚举键值的数组类型。 - RogerKint

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接