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?


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

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

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


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

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.


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


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
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.


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

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


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

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)

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ść.


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


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

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
}

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]);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.