2016-03-02 15 views
13

C'è un modo per avere un enum TypeScript compatibile con le stringhe di JSON?Typescript `enum` dalla stringa JSON

Ad esempio:

enum Type { NEW, OLD } 

interface Thing { type: Type } 

let thing:Thing = JSON.parse('{"type": "NEW"}'); 

alert(thing.type == Type.NEW); // false 

avrei comething.type == Type.NEW per essere vero. O più specificamente, vorrei poter specificare i valori enum da definire come stringhe, non numeri.

Sono consapevole del fatto che posso utilizzare thing.type.toString() == Type[Type.NEW] ma questo è ingombrante e sembra rendere l'annotazione del tipo enum confusa e fuorviante, che sconfigge il suo scopo. Il JSON è tecnicamente non fornendo un valore enum valido, quindi non dovrei digitare la proprietà all'enumerazione.

Quindi quello che sto facendo attualmente invece utilizza un tipo di stringa con costanti statiche:

const Type = { NEW: "NEW", OLD: "OLD" } 

interface Thing { type: string } 

let thing:Thing = JSON.parse('{"type": "NEW"}'); 

alert(thing.type == Type.NEW); // true 

Questo mi fa l'uso che voglio, ma il tipo di annotazione string è troppo ampio e soggetto a errori.

Sono un po 'sorpreso dal fatto che un superset di JavaScript non abbia enumerazioni basate su stringhe. Mi sto perdendo qualcosa? C'è un modo diverso per farlo?


Aggiornamento TS 1.8

Utilizzando string literal types è un'altra alternativa (grazie @basaret), ma per ottenere l'utilizzo enum-come desiderato (sopra) richiede che definisce i valori due volte: una volta in un tipo letterale di stringa, e una volta come un valore (costante o namespace):

type Type = "NEW" | "OLD"; 
const Type = { 
    NEW: "NEW" as Type, 
    OLD: "OLD" as Type 
} 

interface Thing { type: Type } 

let thing:Thing = JSON.parse(`{"type": "NEW"}`); 

alert(thing.type === Type.NEW); // true 

Questo funziona, ma prende un sacco di boilerplate, basta che io non lo uso più o il tempo Per ora spero che lo proposal for string enums alla fine formuli la roadmap.


Aggiorna TS 2.1

Il nuovo keyof type lookup consente al tipo letterale stringa tratte dai tasti di un const o spazio, che rende la definizione un po meno ridondante:

namespace Type { 
    export const OLD = "OLD"; 
    export const NEW = "NEW"; 
} 
type Type = keyof typeof Type; 

interface Thing { type: Type } 

const thing: Thing = JSON.parse('{"type": "NEW"}'); 
thing.type == Type.NEW // true 

Aggiornamento T S 2.4

TypeScript 2.4 added support for string enums!L'esempio precedente diventa:

enum Type { 
    OLD = "OLD", 
    NEW = "NEW" 
} 

interface Thing { type: Type } 
const thing: Thing = JSON.parse('{"type": "NEW"}'); 
alert(thing.type == Type.NEW) // true 

Questo sembra quasi perfetto, ma c'è ancora un po 'di mal di cuore:

  • È ancora devono scrivere il valore due volte, vale a dire OLD = "OLD", e non c'è alcuna convalida che non hai un errore di battitura, come NEW = "MEW" ... questo mi ha già morso in codice reale.
  • Ci sono alcune stranezze (forse degli errori?) Con il modo in cui l'enum è controllato, non è solo una stenografia di tipo letterale, che è ciò che sarebbe veramente corretto. Alcuni problemi che ho urtato:

    enum Color { RED = "RED", BLUE = "BLUE", GREEN = "GREEN" } 
    
    type ColorMap = { [P in Color]: number; } 
    
    declare const color: Color; 
    declare const map: ColorMap; 
    map[color] // Error: Element implicitly has an 'any' type because type 'ColorMap' has no index signature. 
    
    const red: Color = "RED"; // Type '"RED"' is not assignable to type 'Color'. 
    const blue: Color = "BLUE" as "RED" | "BLUE" | "GREEN"; // Error: Type '"RED" | "BLUE" | "GREEN"' is not assignable to type 'Color'. 
    

    Il codice equivalente con enum Color sostituito da stringhe letterali tipi funzionano bene ...

Sì, credo di avere OCD su questo, voglio solo le mie enfole JS perfette. :)

risposta

6

Se si utilizza tipografico prima del rilascio 2.4, c'è un modo per raggiungere questo con enumerazioni lanciando i valori della vostra enum a any.

Un example of your first implementation

enum Type { 
    NEW = <any>"NEW", 
    OLD = <any>"OLD", 
} 

interface Thing { type: Type } 

let thing:Thing = JSON.parse('{"type": "NEW"}'); 

alert(thing.type == Type.NEW); // true 

Typescript 2.4 has built in support for string enums già, quindi non sarebbe più necessario il cast per any e si potrebbe raggiungere senza l'uso di String Literal Union Type, che è ok per la convalida e completamento automatico, ma non così buono per leggibilità e refactoring, a seconda dello scenario di utilizzo.

+0

Grazie! Vorrei aver saputo della "qualsiasi" asserzione prima. Ora sto provando TS 2.4 enum di stringhe, ed è abbastanza vicino a quello che volevo inizialmente ... ma ho trovato alcuni problemi con il modo in cui il tipo TS lo controlla ... – Aaron

+0

@Aaron cool, felice di aiutare! Inoltre, potresti voler controllare il progetto [ts-enums] (https://github.com/LMFinney/ts-enums) in quanto rende la gestione di enum molto versatile e potente per molti casi d'uso –

0

ma la stringa di annotazione del tipo è troppo ampia e soggetta a errori.

Concordato. Una soluzione rapida (se avete il lusso di generazione di codice è possibile automatizzare questo):

interface Thing { type: "NEW" | "OLD" } 

Questi sono chiamati stringhe letterali in un unione. Altro: https://basarat.gitbooks.io/typescript/content/docs/tips/stringEnums.html

+0

Questo sembra promettente. Esamineremo quando eseguiremo l'aggiornamento a TS 1.8. Stavo giocando con esso e non ero in grado di capire come usare i valori letterali delle stringhe come costanti, cioè qualcosa come 'thing.type == Type.NEW'. – Aaron

+1

[Questo è il più vicino che potrei ottenere] (http://www.typescriptlang.org/Playground#src=type%20Type%20%3D%20%22NEW%22%20%7C%20%22OLD%22% 3B% 0Aconst% 20Type% 20% 3D% 20% 7B% 0A% 09NEW% 3A% 20% 22NEW% 22% 2C% 0A% 09OLD% 3A% 20% 22OLD% 22% 0A% 7D% 0A% 0Ainterface% 20Thing% 20% 7B% 20type% 3A% 20Type% 20% 7D% 0A% 0Alet% 20thing% 3AThing% 20% 3D% 20JSON.parse (% 60% 7B% 22type% 22% 3A% 20% 22NEW% 22% 7D% 60)% 3B% 0A% 0Aalert (thing.type% 20% 3D% 3D% 3D% 20Type.NEW)% 3B), ma richiede la definizione di 'Type' e dei valori due volte, una volta come' type' da usare come un'annotazione del tipo di interfaccia, e ancora come 'const' per gli usi in cui ho bisogno dei valori. Questo può essere migliorato? – Aaron

0

Ho utilizzato le funzioni del convertitore come soluzione temporanea. Speriamo che questa discussione viene a risoluzione: https://github.com/Microsoft/TypeScript/issues/1206

enum ErrorCode { 
    Foo, 
    Bar 
} 

interface Error { 
    code: ErrorCode; 
    message?: string; 
} 

function convertToError(obj: any): Error { 
    let typed: Error = obj as Error; 

    // Fix any enums 
    typed.code = ErrorCode[typed.code.toString()]; 
    return typed; 
}