Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatic string enums

A string enum like the following looks quite redundant, tedious to code and prone to errors if you mistype some creating a duplicate (see as in the last one in the following sample)

enum Cmd{
    StartServer = "StartServer",
    StopServer = "StopServer",
    ResumServer1 = "ResumeServer1",
    ResumServer2 = "ResumeServer1"   // this would cause problems
}

I am looking for a way to declare only the elements of the enum and have the values automatically be the symbol name and optionally with a prefix

The farthest I arrived is to something like this:

export function stringifyEnum(enu:any, prefix:string=undefined){   

  Object.keys(enu).forEach( k =>{
    if (isNumber(k))
        enu[k] = prefix+enu[k]
    else
        enu[k] = prefix+k
  })
}

that seems to work:

enum Cmd{
    StartServer,
    StopServer,
    ResumeServer1,
    ResumeServer2
}

stringifyEnum(Cmd,"Cmd")
console.log(Cmd.StartServer)  // --> CmdStartServer

With this approach, so far, the only problem is that typescript considers the enum being numerical and complains in some cases.

Is there a better approach to this (the main target here is a concise enumeration) or do you see hidden risks in this idea?

like image 432
tru7 Avatar asked Jun 08 '18 10:06

tru7


People also ask

Can you have an enum of strings?

Note: you cannot set an enum to string as enums can only have integers. The conversion above simply converts an already existing enum into a string. It is set that every enumeration begins at 0.

Can enums be strings Python?

Enumerations are created using classes. Enums have names and values associated with them. Properties of enum: Enums can be displayed as string or repr.

Can enums be strings C++?

Here we will see how to map some enum type data to a string in C++. There is no such direct function to do so. But we can create our own function to convert enum to string. We shall create a function that takes an enum value as the argument, and we manually return the enum names as a string from that function.

When should I use TypeScript enums?

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.


2 Answers

I have all but abandoned enums in TypeScript in favor of string literal union types.

For your example that would look like:

type Cmd = 'StartServer' | 'StopServer' | 'ResumeServer1' | 'ResumeServer2';

This approach will give you the same benefits in compile-time checks as an enum:

function foo(cmd: Cmd) {}

foo('StartServer'); // OK
foo('BeginServer'); // error
like image 130
Robby Cornelissen Avatar answered Nov 01 '22 12:11

Robby Cornelissen


I don't think you can programmatically do a prefix so that the type system understands it; that would require some type operations that are not part of the language yet. There is an existing suggestion for this feature, but it doesn't look like anyone is working on it.


As for getting the key and the value to be exactly the same, you can write a function that takes a list of strings and produces a strongly-typed "enum"-like map:

function enumize<K extends string>(...args: K[]): {[P in K]: P} {
  const ret = {} as {[P in K]: P};
  args.forEach(k => ret[k]=k);
  return ret;
}
const Cmd = enumize("StartServer", "StopServer", "ResumeServer1", "ResumeServer2");

It is recognized that the value Cmd has the type { StartServer: "StartServer"; ... }. You will be able to access elements as expected:

console.log(Cmd.StartServer); // works

An official enum also creates some named types, which enumize() doesn't. To fully replicate the types, you would have to more work:

type Cmd = keyof typeof Cmd; // allows you to refer to the type Cmd as before

That's the union type mentioned in @RobbyCornelissen's answer. You need it if you're going to refer to Cmd as a type, as in:

declare function requireCmd(cmd: Cmd); // the Cmd here is a type
requireCmd(Cmd.StopServer); // works

If you need to refer to the types of each enum element, you have to do even more busywork, including ugly code duplication:

namespace Cmd {
  export type StartServer = typeof Cmd.StartServer
  export type StopServer = typeof Cmd.StopServer
  export type ResumeServer1 = typeof Cmd.ResumeServer1
  export type ResumeServer2 = typeof Cmd.ResumeServer2
}

That stuff would be needed to refer to types like Cmd.StopServer as in:

interface CommandInfo {
  kind: Cmd;
}

interface StopServerInfo extends CommandInfo {
   kind: Cmd.StopServer;  // need namespace for this line
   commandIssueDate: Date;
   numberOfUsersForcedOffline: number;
}

But if you're not going to do that very much, then you can leave out the namespace stuff... you can always use the type typeof Cmd.StopServer instead.


Hope that helps; good luck.


like image 21
jcalz Avatar answered Nov 01 '22 11:11

jcalz