Using Enum Values as String Literals

Using Enum values as String literals

You can't. I think you have FOUR options here. All four offer a solution but with a slightly different approach...

Option One: use the built-in name() on an enum. This is perfectly fine if you don't need any special naming format.

    String name = Modes.mode1.name(); // Returns the name of this enum constant, exactly as declared in its enum declaration.

Option Two: add overriding properties to your enums if you want more control

public enum Modes {
mode1 ("Fancy Mode 1"),
mode2 ("Fancy Mode 2"),
mode3 ("Fancy Mode 3");

private final String name;

private Modes(String s) {
name = s;
}

public boolean equalsName(String otherName) {
// (otherName == null) check is not needed because name.equals(null) returns false
return name.equals(otherName);
}

public String toString() {
return this.name;
}
}

Option Three: use static finals instead of enums:

public final class Modes {

public static final String MODE_1 = "Fancy Mode 1";
public static final String MODE_2 = "Fancy Mode 2";
public static final String MODE_3 = "Fancy Mode 3";

private Modes() { }
}

Option Four: interfaces have every field public, static and final:

public interface Modes {

String MODE_1 = "Fancy Mode 1";
String MODE_2 = "Fancy Mode 2";
String MODE_3 = "Fancy Mode 3";
}

Typescript: string literal union type from enum

See TS4.1 ANSWER:

type WeekdayType = `${Weekday}`;

PRE TS-4.1 ANSWER:

This can't be done programmatically... you're trying to convert the type Weekday, which is Weekday.MONDAY | Weekday.TUESDAY | Weekday.WEDNESDAY, to the type WeekdayType which is "mon" | "tue" | "wed". This conversion is a form of widening, since Weekday is a subtype of WeekdayType:

type WeekdayExtendsWeekdayType = 
Weekday extends WeekdayType ? true : false
// type WeekdayExtendsWeekdayType = true

Unfortunately the compiler doesn't give you a handle to remove an "enum"-ness from the enum type and leave you with plain literal types.


So, workarounds? Maybe you don't actually need an enum, but can make do with an object whose property values are string literals:

const lit = <V extends keyof any>(v: V) => v;
const Weekday = {
MONDAY: lit("mon"),
TUESDAY: lit("tue"),
WEDNESDAY: lit("wed")
}
type Weekday = (typeof Weekday)[keyof typeof Weekday],

If you inspect it, the value named Weekday behaves like an enum object:

console.log(Weekday.TUESDAY); // tue

while the type named Weekday behaves like the union of string values "mon" | "tue" | "wed" that you were calling WeekdayType:

const w: Weekday = "wed"; // okay
const x: Weekday = "xed"; // error

So in this workaround, there is no "enum"-ness, and therefore no need to distinguish the type Weekday from the type WeekdayType. It's a little different from an actual enum (which includes types like Weekday.MONDAY, which you'd have to represent as the cumbersome typeof Weekday.MONDAY or create a different type alias for it), but it might behave similarly enough to be useful. Does that work for you?

Difference between string enums and string literal types in TS

The key thing to understand is that the values of string enums are opaque.

The intended use case for a string enum is that you don't want other code to know or care what the literal string backing MyKeyType.FOO is. This means that you won't be able to, say, pass the literal string "bar" to a function accepting a MyKeyType -- you'll have to write MyKeyType.BAR instead.

Make a one to one mapping from string to enum variant without using a match statement

You really want to use a library for this. You can write the macro yourself, but someone else has already made a battle-tested version of what you're trying to do.

Use strum's EnumString derive:

In Cargo.toml:

[dependencies]
strum = "0.24"
strum_macros = "0.24"

In your source code: (eg main.rs)

use std::str::FromStr;
use strum_macros::EnumString;

#[derive(Debug, EnumString)]
enum MyEnum {
Foo,
Bar,
Baz,
}

fn main() {
dbg!(MyEnum::from_str("Foo").unwrap());
}

Gives you

[src/main.rs:12] MyEnum::from_str("Foo").unwrap() = Foo

For more details see The documentation for the FromString derive and strum's readme.

Is an enum with string (quoted text) in Dart possible?

The best structure is a map. Or two maps, since you want to map both ways.

You are mapping from String to int, and from int to String. That's two value mappings.

It has nothing to do with enums, which are multiple instances of the same type.

I'm sure you can find some "BiDirectionalMap" out there, but it's unlikely to be worth it since your mapping doesn't need to be updated dyamically.
Two constant maps will do:

const somethingMap = {
'qwe': 1,
'rty': 2,
'uio': 4,
};
const somethingReverseMap = {
1: 'qwe',
2: 'rty',
4: 'uio',
};
int something(String code) => somethingMap[code] ??
throw ArgumentError.value(code, "code", "Not a valid code");
String somethingReverse(int value) => somethingReverseMap[value] ??
throw ArgumentError.value(value, "value", "Not a valid value");

Nothing you do will get safer than this. You are switching on the runtime string values, not allowing every string value, so you can't use the type system.

Angular Get value from string Enum using integer

Managed to have custom strings in this way:

Shared/enums-descriptions.ts

export const MyTypesEnumDescription: {
[key in MyTypesEnum]: string;
} = {
[MyTypesEnum.Standard]: 'Standard',
[MyTypesEnum.Advanced]: 'Advanced Stage',
[MyTypesEnum.Intermediate]: 'Intermediate Stage',
};

MyClass.ts

public selectBoxItemList = [
new SelectListItem({
value: MyTypesEnum.Advanced,
text: MyTypesEnumDescription.InvalidCall,
}),

Combining enum values and checking if another value is in that enum

First of all, it worth using immutable object instead of numerical enums.
See example:

const myEnum = {
a: 1,
b: 2
} as const

const mySecondEnum = {
c: 3,
d: 4
} as const

const myThirdEnum = {
e: 5,
f: 6
} as const

Now, enumValuesArray has an expected type:

// (1 | 2 | 3 | 4)[]
const enumValuesArray = [...Object.values(myEnum), ...Object.values(mySecondEnum)];

As for the error with Array.prototype.includes. It is known issue. TS only allows you to use numbers which are exists in enumValuesArray.

There is a generic and a little bit verbose workaround.
Pros: no need type assertion
Cons: verbose and requires you to curry.

Full example:

const myEnum = {
a: 1,
b: 2
} as const

const mySecondEnum = {
c: 3,
d: 4
} as const

const myThirdEnum = {
e: 5,
f: 6
} as const

type Values<T> = T[keyof T]

type Primitives =
| string
| number
| bigint
| boolean
| symbol
| null
| undefined

type InferPrimitive<T, P> = P extends any ? T extends P ? P : never : never;

type Inference<T> = InferPrimitive<T, Primitives>

const withTuple = <
List extends Primitives[]
>(list: readonly [...List]) =>
(prop: Inference<List[number]>):
prop is Inference<List[number]> & List[number] =>
list.includes(prop)

// (1 | 2 | 3 | 4)[]
const enumValuesArray = [...Object.values(myEnum), ...Object.values(mySecondEnum)];

const includes = withTuple(enumValuesArray);

type DistributeValues<T> = T extends any ? Values<T> : never

// 1 | 2 | 3 | 4 | 5 | 6
type AllowedValues = DistributeValues<typeof myEnum | typeof mySecondEnum | typeof myThirdEnum>

/** Here, b can be a number from 1 to 6 */
const b: AllowedValues = 5; // ok

/** Why is typescript complaining here? I understand, that b CAN be 5 or 6 (as it is), but that is exactly the thing I want to check */
if (includes(b)) {
console.log("Included");
} else console.log("Not included");

includes('a') // expected error

Playground

More explanation regarding includes you will find in my article

InferPrimitive - will unnnarow literal type to much wider type. For instance InferPrimitive<42> will return number.

Write generic method which takes enum and string as arguments

The only thing you need from both enums in this code:

    Arrays.stream(Action.values()).forEach(e ->{
if(e.getValue().equalsIgnoreCase(sp)){
//System.out.println(sp); Some work to do here

}
} );

is the getValue method.

So you can create an interface that has this method:

interface HasValue {
String getValue();
}

Make both enums implement the interface:

public enum Action implements HasValue {
...
}

public enum Days implements HasValue {
...
}

Then you can write a generic method:

public <T extends HasValue> void foo(T[] values, String sp) {
Arrays.stream(values).forEach(e ->{
if(e.getValue().equalsIgnoreCase(sp)){
//System.out.println(sp);

}
});
}

You can call it like this:

foo(Action.values(), sp);
foo(Days.values(), sp);

The method doesn't actually have to be generic. You could just do:

public void foo(HasValue[] values, String sp) {
...
}

If you can't change Days or Action, you can use a functional interface instead:

public <T> void foo(T[] values, Function<T, String> getValue, String sp) {
Arrays.stream(values).forEach(e ->{
if(getValue.apply(e).equalsIgnoreCase(sp)){
//System.out.println(sp);

}
});
}

// usage:

foo(Action.values(), Action::getValue, sp);
foo(Days.values(), Days::getValue, sp);


Related Topics



Leave a reply



Submit