Wyliczenie TypeScript do tablicy obiektów

110

Mam wyliczenie zdefiniowane w ten sposób:

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

Chciałbym jednak, aby był reprezentowany jako tablica / lista obiektów z naszego API, jak poniżej:

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

Czy istnieje prosty i natywny sposób, aby to zrobić, czy też muszę zbudować funkcję, która rzutuje wyliczenie zarówno na int, jak i na ciąg, i zbudować obiekty w tablicy?

AnimaSola
źródło
Wyliczenia to rzeczywiste obiekty, które istnieją w czasie wykonywania. Możesz więc odwrócić mapowanie, wykonując coś takiego: GoalProgressMeasurements[GoalProgressMeasurements.Completed_Tasks]aby uzyskać nazwę wyliczenia. Nie wiem, czy to pomaga.
Diullei
Czy możesz lepiej opisać „z naszego API”, może podać przykład użycia
gilamran

Odpowiedzi:

52

Trudnym problemem jest to, że TypeScript „podwaja” mapowanie wyliczenia w emitowanym obiekcie, dzięki czemu można uzyskać do niego dostęp zarówno za pomocą klucza, jak i wartości.

enum MyEnum {
    Part1 = 0,
    Part2 = 1
}

zostanie wyemitowany jako

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

Dlatego przed mapowaniem należy najpierw przefiltrować obiekt. Tak więc rozwiązanie @Diullei ma właściwą odpowiedź. Oto moja realizacja:

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

Użyj tego w ten sposób:

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

console.log(ToArray(GoalProgressMeasurements));
user8363
źródło
1
mmm, jeśli enum MyEnum { Part1 = 0, Part2 = 1 }zamienia się w { Part1: 0, Part2: 1, 0: 'Part1', 1: 'Part2' } to, dlaczego po console.log(Object.values(MyEnum))prostu drukuje tylko 0,1?
Juan José Ramírez
@ JuanJoséRamírez gdzie to widzisz? Dla mnie Object.values(MyEnum)ocenia się na["Part1", "Part2", 0, 1]
user8363 Kwietnia
Właśnie wydrukowałem console.log(Object.values(MyEnum))w moim komponencie. Używam kątowej, nie jestem pewien, czy to jest powiązane. Nie mam takiego doświadczenia w TypeScript
Juan José Ramírez
czy zachowanie może się zmienić w różnych wersjach TS?
Juan José Ramírez
3
Sprawdzałem dokumenty docs typescriptlang.org/docs/handbook/release-notes/ ... i wygląda na to, że wyliczenia typu string mają inne zachowanie. W ogóle nie są generowane odwrotne odwzorowanie. W moim kodzie użyłem wyliczenia ciągu, a nie ciągu w tym przykładzie.
Juan José Ramírez
46

Jeśli używasz ES8

Tylko w tym przypadku będzie działać idealnie. To da ci tablicę wartości podanego wyliczenia .

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

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

Otrzymasz colorValueArraytaki [ 'WHITE', 'BLACK', 'BLUE', 0, 1, 3 ]. Wszystkie klucze będą znajdować się w pierwszej połowie tablicy, a wszystkie wartości w drugiej połowie.

Nawet ten rodzaj wyliczenia będzie działał dobrze

enum Operation {
    READ,
    WRITE,
    EXECUTE
}

Ale to rozwiązanie nie zadziała w przypadku heterogenicznych wyliczeń, takich jak to

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}
Jai Prak
źródło
9
Pamiętaj, że spowoduje to duplikaty. Wartość ciągu i wartość liczbowa dla każdego elementu, czyli typ, (string | YourEnumType)[]który nie jest tym, czego możesz chcieć w każdym przypadku.
Christian Ivicevic
czy jest gwarantowane, że pierwsza połowa będzie kluczami, a druga połowa wartościami? jakieś odniesienie?
Neekey
1
Object.values()nie jest częścią ES6. Jest częścią ES2017.
atiyar
Zauważ, że nie ma czegoś takiego jak ES8; jak mówi @atiyar, nazywa się ES2017.
Heretic Monkey
37

Wyliczenia to rzeczywiste obiekty istniejące w czasie wykonywania. Możesz więc odwrócić mapowanie, wykonując coś takiego:

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

Na tej podstawie możesz użyć następującego kodu:

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

Źródła: https://www.typescriptlang.org/docs/handbook/enums.html

Diullei
źródło
3
nie musisz zapisywać wartości domyślnych, = 2dopóki = 5- Cokolwiek później nie = 1daje automatycznie +1.
sebilasse
9
Może nie musisz, ale jest bardziej wyrazisty. Co sprawia, że ​​jest lepiej IMHO.
SubliemeSiem
5
uwaga, to nie działa dla wyliczeń wartości ciągów
Bashar Ali Labadi
21

Łatwe rozwiązanie. Możesz użyć następującej funkcji, aby przekonwertować Enum na tablicę obiektów.

 buildGoalProgressMeasurementsArray(): Object[] {

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

Jeśli chcesz usunąć to podkreślenie, możemy użyć wyrażenia regularnego w następujący sposób:

buildGoalProgressMeasurementsArray(): Object[] {

    return Object.keys(GoalProgressMeasurements)
              .map(key => ({ id: GoalProgressMeasurements[key], name: key.replace(/_/g, ' ') }))
 }
Manoj Shrestha
źródło
8
powinieneś przefiltrować klucze typu numer Object.keys(GoalProgressMeasurements) .filter(key => typeof GoalProgressMeasurements[key] === 'number') .map(key => ({ id: GoalProgressMeasurements[key], name: key }))
Salvador Rubio Martinez
13

używam

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

Prosta 1 linia, która spełnia swoje zadanie.

Wykonuje zadanie w 3 prostych krokach
- ładuje kombinację kluczy i wartości za pomocą Object.entries.
- Filtruje nieliczby (ponieważ maszynopis generuje wartości do wyszukiwania wstecznego).
- Następnie mapujemy go na obiekt tablicy, który nam się podoba.

CMS
źródło
Nie jest kompatybilny z IE z front-endem (nie powinien obsługiwać tj. To świetna odpowiedź ... ale myślę, że klienci). Mam nadzieję, że Babel to
wyjdzie
string enum jest łatwe, po prostu zróbObject.values(GoalProgressMeasurement)
CMS
11

To da ci tablicę wartości wyliczeniowych:

 Object.values(myEnum);
Gaurav Panwar
źródło
@PauloQueiroz Przejdzie na górę automatycznie, jeśli wzrośnie liczba głosów za. Dzięki!
Gaurav Panwar
Ponieważ nie daje to właściwego wyniku, sprawdź stackoverflow.com/a/57266281/3548345
walox
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]);
    }
}
Liam Kernighan
źródło
1
Dziękuję za tych pomocników. Bardzo przydatne.
user906573
4

Nie podobała mi się żadna z powyższych odpowiedzi, ponieważ żadna z nich nie obsługuje poprawnie mieszaniny ciągów / liczb, które mogą być wartościami w wyliczeniach TypeScript.

Poniższa funkcja jest zgodna z semantyką wyliczeń TypeScript, aby zapewnić odpowiednią Mapę kluczy do wartości. Stamtąd uzyskanie tablicy obiektów, samych kluczy lub samych wartości jest trywialne.

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

Przykładowe zastosowanie:

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

Zwrócę również uwagę, że OP myśli o wyliczeniach wstecz. „Klucz” w wyliczeniu jest technicznie po lewej stronie, a wartość po prawej stronie. TypeScript umożliwia powtarzanie wartości na RHS tyle, ile chcesz.

MgSam
źródło
4

Najpierw otrzymujemy tablicę kluczy dla tego wyliczenia. Następnie za pomocą funkcji map () konwertujemy dane do żądanego formatu. id jest uzyskiwany z klucza, nazwa jest uzyskiwana z wyliczenia przez ten sam klucz.

const converted = Object.keys(GoalProgressMeasurements).map(key => {
        return {
            id: GoalProgressMeasurements[key],
            name: key,
        };
    });
VaCool
źródło
2
Witamy w stackoverflow. Odpowiadając na pytania, dobrze jest wyjaśnić, co robi Twój fragment kodu. Aby uzyskać więcej informacji, zobacz tutaj: Jak odpowiedzieć
Djensen
Rozważ dodanie wyjaśnień lub szczegółów do swojej odpowiedzi. Chociaż może odpowiedzieć na pytanie, wystarczy dodać fragment kodu jako odpowiedź, ale nie pomaga OP ani przyszłym członkom społeczności w zrozumieniu problemu lub proponowanego rozwiązania.
Maxim
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);

nico
źródło
Prosimy o umieszczanie odpowiedzi zawsze w kontekście, a nie tylko wklejanie kodu. Więcej informacji znajdziesz tutaj .
gehbiszumeis
1

Jest proste rozwiązanie, więc kiedy uruchomisz Object.keys(Enum)to, otrzymasz tablicę wartości i kluczy, w pierwszym wycinku wartości i w drugim kluczach, więc dlaczego nie zwrócimy po prostu drugiego wycinka, ten kod poniżej działa dla mnie .

enum Enum {
   ONE,
   TWO,
   THREE,
   FOUR,
   FIVE,
   SIX,
   SEVEN
}
const keys = Object.keys(Enum); 
console.log(keys.slice(keys.length / 2));
دانیال مدیری
źródło
Ciekawe, dlaczego głosowano w dół? Miałem ten sam pomysł. Czy martwiłbyś się, że TypeScript zmieni się i nie będzie już tworzyć wyliczeń w ten sam sposób?
Tony Smith
1

Oto prosta funkcja z poprawnym pisaniem, której używam

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

Przykład użycia:

enum Colors {
  Red = 1,
  Blue = 2,
}
enumToArray(Colors)
Viktor Chernodub
źródło
Niezłe. Dzięki!!!
Mauricio Contreras
0

Możesz to zrobić w ten sposób:

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"),
}

Możesz go używać w ten sposób:

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;

W razie potrzeby możesz rozszerzyć GoalProgressMeasurement o dodatkowe właściwości obiektu. Używam tego podejścia do każdego wyliczenia, które powinno być obiektem zawierającym więcej niż wartość.

Boban Stojanovski
źródło
0

Ponieważ wyliczenia z wartościami ciągów różnią się od tych, które mają wartości liczbowe, lepiej jest filtrować wartości inne niż liczby z rozwiązania @ user8363.

Oto, w jaki sposób możesz uzyskać wartości z wyliczenia zarówno ciągów, jak i liczb mieszanych:

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

Islem Penywis
źródło
0

Jestem zaskoczony, że w wątku TypeScript nikt nie dał prawidłowej funkcji TypeScript z obsługą pisania. Oto odmiana rozwiązania @ 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]
  );
}
Daniel Kmak
źródło
0

Nie sądzę, aby kolejność mogła być zagwarantowana, w przeciwnym razie łatwo byłoby wyciąć drugą połowę Object.entrieswyniku i zmapować stamtąd.

Jedyny (bardzo drobny) problem z powyższymi odpowiedziami jest taki

  • istnieje wiele niepotrzebnych konwersji typu między ciągiem a liczbą.
  • wpisy są iterowane dwukrotnie, gdy pojedyncza iteracja jest równie przejrzysta i skuteczna.
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
}
Brent
źródło
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]);
}
Rip Ryness
źródło