Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript `enum` from JSON string

Is there any way to have a TypeScript enum compatible with strings from JSON?

For example:

enum Type { NEW, OLD }  interface Thing { type: Type }  let thing:Thing = JSON.parse('{"type": "NEW"}');  alert(thing.type == Type.NEW); // false 

I would like thing.type == Type.NEW to be true. Or more specifically, I wish I could specify the enum values to be defined as strings, not numbers.

I am aware that I can use thing.type.toString() == Type[Type.NEW] but this is cumbersome and seems to make the enum type annotation confusing and misleading, which defeats its purpose. The JSON is technically not supplying a valid enum value, so I shouldn't type the property to the enum.

So what I am currently doing instead is using a string type with static constants:

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

This gets me the usage I want, but the type annotation string is way too broad and error prone.

I'm a bit surprised that a superset of JavaScript doesn't have string based enums. Am I missing something? Is there a different way this can be done?


Update TS 1.8

Using string literal types is another alternative (thanks @basaret), but to get the desired enum-like usage (above) it requires defining your values twice: once in a string literal type, and once as a value (constant or 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 

This works but takes a lot of boilerplate, enough that I don't use it most of the time. For now I'm hoping the proposal for string enums will eventually make the roadmap.


Update TS 2.1

The new keyof type lookup allows for the string literal type to be generated from the keys of a const or namespace, which makes the definition a little less redundant:

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 

Update TS 2.4

TypeScript 2.4 added support for string enums! The above example becomes:

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

This looks nearly perfect, but there's still some heartache:

  • You still have to write the value twice, ie OLD = "OLD", and there's no validation that you don't have a typo, like NEW = "MEW"... this has already bitten me in real code.
  • There's some oddities (perhaps bugs?) with how the enum is type checked, its not just a string literal type shorthand, which is what would be truly correct. Some issues I've bumped into:

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

    The equivalent code with enum Color replaced by string literal types work fine...

Yeah, I think I have OCD about this, I just want my perfect JS enums. :)

like image 670
Aaron Beall Avatar asked Mar 02 '16 23:03

Aaron Beall


People also ask

How do I convert a string to enum in TypeScript?

To convert a string to an enum: Use keyof typeof to cast the string to the type of the enum. Use bracket notation to access the corresponding value of the string in the enum.

Are TypeScript enums bad?

The they are useless at runtime argument This is a false argument for typescript in general, let alone Enums. and agree, if at runtime some code tries to change the values of one of your enums, that would not throw an error and your app could start behaving unexpectedly ( that is why Object.

How do I use string enums?

In summary, to make use of string-based enum types, we can reference them by using the name of the enum and their corresponding value, just as you would access the properties of an object. At runtime, string-based enums behave just like objects and can easily be passed to functions like regular objects.

How do I use enum values in TypeScript?

In TypeScript, enums, or enumerated types, are data structures of constant length that hold a set of constant values. Each of these constant values is known as a member of the enum. Enums are useful when setting properties or values that can only be a certain number of possible values.


1 Answers

If you are using Typescript before the 2.4 release, there is a way to achieve that with enums by casting the values of your enum to any.

An 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 already, so the cast to any would be no longer necessary and you could achieve it without the use of String Literal Union Type, which is ok for validation and autocomplete, but not so good for readability and refactoring, depending on the usage scenario.

like image 130
Felipe Sabino Avatar answered Sep 21 '22 13:09

Felipe Sabino