Enumeração de TypeScript para matriz de objeto


110

Eu tenho um enum definido desta forma:

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

No entanto, gostaria que fosse representado como um array / lista de objetos de nossa API como a seguir:

[{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'}]

Existe uma maneira fácil e nativa de fazer isso ou eu tenho que construir uma função que converta o enum para um int e uma string e construir os objetos em uma matriz?


Enums são objetos reais que existem em tempo de execução. Portanto, você pode reverter o mapeamento fazendo algo assim: GoalProgressMeasurements[GoalProgressMeasurements.Completed_Tasks]obter o nome do enum. Não sei se isso ajuda.
Diullei

Você pode dar uma descrição melhor para "de nossa API", talvez dar um exemplo de uso
gilamran

Respostas:


52

Uma parte complicada é que o TypeScript fará o mapeamento 'duplo' do enum no objeto emitido, para que ele possa ser acessado por chave e valor.

enum MyEnum {
    Part1 = 0,
    Part2 = 1
}

será emitido como

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

Portanto, você deve filtrar o objeto antes de mapear. Portanto, a solução de @Diullei tem a resposta certa. Aqui está minha implementação:

// 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]);
}

Use-o assim:

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

console.log(ToArray(GoalProgressMeasurements));

1
mmm se enum MyEnum { Part1 = 0, Part2 = 1 }transforma em { Part1: 0, Part2: 1, 0: 'Part1', 1: 'Part2' } então, por que quando você console.log(Object.values(MyEnum))imprime apenas 0,1?
Juan José Ramírez

@ JuanJoséRamírez onde você vê isso? Para mim Object.values(MyEnum)avalia para["Part1", "Part2", 0, 1]
user8363

Acabei de imprimir console.log(Object.values(MyEnum))no meu componente. Estou usando angular, não tenho certeza se está relacionado. Não tenho tanta experiência em TypeScript
Juan José Ramírez

o comportamento poderia mudar por meio de diferentes versões de TS?
Juan José Ramírez

3
Estive verificando os documentos typescriptlang.org/docs/handbook/release-notes/… e parece que os enums de string têm um comportamento diferente. Eles não recebem um mapeamento reverso gerado. Em meu código, eu estava usando um enum de string, não a string neste exemplo.
Juan José Ramírez

46

Se você estiver usando ES8

Apenas neste caso funcionará perfeitamente bem. Ele lhe dará a matriz de valor do enum fornecido .

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

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

Você vai ficar colorValueArrayassim [ 'WHITE', 'BLACK', 'BLUE', 0, 1, 3 ]. Todas as chaves estarão na primeira metade da matriz e todos os valores na segunda metade.

Mesmo este tipo de enum funcionará bem

enum Operation {
    READ,
    WRITE,
    EXECUTE
}

Mas esta solução não funcionará para enums heterogêneos como este

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

9
Lembre-se de que isso resultará em duplicatas. O valor da string e o valor numérico para cada elemento, ou seja, um tipo (string | YourEnumType)[]que não é o que você deseja em cada caso.
Christian Ivicevic

é garantido que a primeira metade serão as chaves e a segunda metade os valores? alguma referência?
Neekey

1
Object.values()não faz parte do ES6. Faz parte do ES2017.
atiyar

Observe que não existe ES8; como @atiyar diz, é chamado ES2017.
Macaco herege

37

Enums são objetos reais que existem em tempo de execução. Portanto, você pode reverter o mapeamento fazendo algo assim:

let value = GoalProgressMeasurements.Not_Measured;
console.log(GoalProgressMeasurements[value]);
// => Not_Measured

Com base nisso, você pode usar o seguinte código:

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);

Referência: https://www.typescriptlang.org/docs/handbook/enums.html


3
você não precisa escrever os padrões = 2até = 5- Qualquer coisa depois = 1é +1 automaticamente.
sebilasse de

9
Talvez você não precise, mas é mais expressivo. O que o torna melhor IMHO.
SubliemeSiem

5
apenas uma observação que não funciona para enums de valor de string
Bashar Ali Labadi

21

Solução fácil. Você pode usar a seguinte função para converter seu Enum em uma matriz de objetos.

 buildGoalProgressMeasurementsArray(): Object[] {

    return Object.keys(GoalProgressMeasurements)
              .map(key => ({ id: GoalProgressMeasurements[key], name: key }))
 }

Se você precisasse remover esse sublinhado, poderíamos usar regex da seguinte maneira:

buildGoalProgressMeasurementsArray(): Object[] {

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

8
você deve filtrar as chaves do número do tipo Object.keys(GoalProgressMeasurements) .filter(key => typeof GoalProgressMeasurements[key] === 'number') .map(key => ({ id: GoalProgressMeasurements[key], name: key }))
Salvador Rubio Martinez

13

eu uso

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

Uma linha simples que faz o trabalho.

Ele faz o trabalho em 3 etapas simples
- Carrega a combinação de chaves e valores usando Object.entries.
- Filtra os não números (uma vez que o texto digitado gera os valores para pesquisa reversa).
- Em seguida, mapeamos para o objeto de matriz que gostamos.


Não é compatível com o IE com front-end (não deve suportar, ou seja, é uma ótima resposta ... mas acho que os clientes). Esperando que a babel transpire, mas
usando

string enum é fácil, basta fazerObject.values(GoalProgressMeasurement)
CMS


10
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
Obrigado por esses ajudantes. Muito útil.
user906573,

4

Primeiro, obtemos uma matriz de chaves para este enum. Então, usando a função map (), convertemos os dados para o formato desejado. id é obtido a partir da chave, o nome é obtido a partir de enum pela mesma chave.

const converted = Object.keys(GoalProgressMeasurements).map(key => {
        return {
            id: GoalProgressMeasurements[key],
            name: key,
        };
    });

2
Bem-vindo ao stackoverflow. Ao responder às perguntas, é uma boa ideia explicar o que seu trecho de código faz. Para obter mais informações, consulte aqui: Como responder
Djensen


Considere adicionar alguma explicação ou detalhes à sua resposta. Embora possa responder à pergunta, apenas adicionar um trecho de código como resposta não ajuda o OP ou os futuros membros da comunidade a entender o problema ou a solução proposta.
Máximo de

3

Não gostei de nenhuma das respostas acima porque nenhuma delas trata corretamente a mistura de strings / números que podem ser valores em enums do TypeScript.

A função a seguir segue a semântica dos enums do TypeScript para fornecer um mapa adequado de chaves para valores. A partir daí, obter uma matriz de objetos ou apenas as chaves ou apenas os valores é trivial.

/**
 * 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;
}

Exemplo de uso:

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

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

lets 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

Também destacarei que o OP está pensando em enums ao contrário. A "chave" no enum está tecnicamente no lado esquerdo e o valor está no lado direito. O TypeScript permite que você repita os valores no RHS tanto quanto desejar.


1

enum GoalProgressMeasurements {
    Percentage = 1,
    Numeric_Target = 2,
    Completed_Tasks = 3,
    Average_Milestone_Progress = 4,
    Not_Measured = 5
}
    
const array = []
    
for (const [key, value] of Object.entries(GoalProgressMeasurements)) {
    if (!Number.isNaN(Number(key))) {
        continue;
    }

    array.push({ id: value, name: key.replace('_', '') });
}

console.log(array);


Por favor, coloque sua resposta sempre no contexto, em vez de apenas colar o código. Veja aqui para mais detalhes.
gehbiszumeis

1

Existe uma solução simples, então quando você executar Object.keys(Enum)isso vai lhe dar uma matriz de valores e chaves, nos valores da primeira fatia e na segunda chave, então por que não retornamos apenas a segunda fatia, este código abaixo funciona para mim .

enum Enum {
   ONE,
   TWO,
   THREE,
   FOUR,
   FIVE,
   SIX,
   SEVEN
}
const keys = Object.keys(Enum); 
console.log(keys.slice(keys.length / 2));

Curioso por que isso foi rejeitado? Eu tive a mesma ideia. A preocupação seria que o TypeScript mudasse e não fizesse mais enums da mesma maneira?
Tony Smith

1

Aqui está a função simples com a digitação correta que eu uso

/**
 * Helper to produce an array of enum values.
 * @param enumeration Enumeration object.
 */
export function enumToArray<T, G extends keyof T = keyof T>(enumeration: T): T[G][] {
  // tslint:disable: comment-format

  // enum Colors {
  //   WHITE = 0,
  //   BLACK = 1,
  // }
  // Object.values(Colors) will produce ['WHITE', 'BLACK', 0, 1]

  // So, simply slice the second half
  const enumValues = Object.values(enumeration);
  return enumValues.slice(enumValues.length / 2, enumValues.length) as T[G][];
}

Exemplo de uso:

enum Colors {
  Red = 1,
  Blue = 2,
}
enumToArray(Colors)

Agradável. Obrigado!!!
Mauricio Contreras

0

Você pode fazer isso desta forma:

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

export class GoalProgressMeasurement {
    constructor(public goalProgressMeasurement: GoalProgressMeasurements, public name: string) {
    }
}

export var goalProgressMeasurements: { [key: number]: GoalProgressMeasurement } = {
    1: new GoalProgressMeasurement(GoalProgressMeasurements.Percentage, "Percentage"),
    2: new GoalProgressMeasurement(GoalProgressMeasurements.Numeric_Target, "Numeric Target"),
    3: new GoalProgressMeasurement(GoalProgressMeasurements.Completed_Tasks, "Completed Tasks"),
    4: new GoalProgressMeasurement(GoalProgressMeasurements.Average_Milestone_Progress, "Average Milestone Progress"),
    5: new GoalProgressMeasurement(GoalProgressMeasurements.Not_Measured, "Not Measured"),
}

E você pode usá-lo assim:

var gpm: GoalProgressMeasurement = goalProgressMeasurements[GoalProgressMeasurements.Percentage];
var gpmName: string = gpm.name;

var myProgressId: number = 1; // the value can come out of drop down selected value or from back-end , so you can imagine the way of using
var gpm2: GoalProgressMeasurement = goalProgressMeasurements[myProgressId];
var gpmName: string = gpm.name;

Você pode estender o GoalProgressMeasurement com propriedades adicionais do objeto conforme necessário. Estou usando essa abordagem para cada enumeração que deve ser um objeto contendo mais do que um valor.


0

Uma vez que enums com valores de Strings diferem daqueles que têm valores de número, é melhor filtrar os não-números da solução @ user8363.

Aqui está como você pode obter valores de enum de strings, números de mistos:

    //Helper
    export const StringIsNotNumber = value => isNaN(Number(value)) === true;
    
    // Turn enum into array
    export function enumToArray(enumme) {
      return Object.keys(enumme)
       .filter(StringIsNotNumber)
       .map(key => enumme[key]);
    }


0

Estou surpreso em um thread de TypeScript que ninguém forneceu uma função TypeScript válida com suporte para digitação. Aqui está uma variação da solução @ user8363:

const isStringNumber = (value: string) => isNaN(Number(value)) === false;

function enumToArray<T extends {}>(givenEnum: T) {
  return (Object.keys(givenEnum).filter(isStringNumber) as (keyof T)[]).map(
    (key) => givenEnum[key]
  );
}

0

Eu não acho que a ordem pode ser garantida, caso contrário, seria fácil cortar a segunda metade do Object.entriesresultado e mapear a partir daí.

O único (muito pequeno) problema com as respostas acima é que

  • há muita conversão de tipo desnecessária entre string e número.
  • as entradas são iteradas duas vezes quando uma única iteração é tão limpa e eficaz.
type StandardEnum = { [id: string]: number | string; [nu: number]: string;}

function enumToList<T extends StandardEnum> (enm: T) : { id: number; description: string }[] {
    return Object.entries(enm).reduce((accum, kv) => {
        if (typeof kv[1] === 'number') {
            accum.push({ id: kv[1], description: kv[0] })
        }
        return accum
    }, []) // if enum is huge, perhaps pre-allocate with new Array(entries.length / 2), however then push won't work, so tracking an index would also be required
}

0
function enumKeys(_enum) {
  const entries = Object.entries(_enum).filter(e => !isNaN(Number(e[0])));
  if (!entries.length) {
    // enum has string values so we can use Object.keys
    return Object.keys(_enum);
  }
  return entries.map(e => e[1]);
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.