Reputation: 578
I want to make a generic type that checks the following on an enum:
So in which case, the following enums would be considered "correct":
enum correct1 {
bar = 'bar',
baz = 'baz',
}
enum correct2 {
quux = 'quux',
}
but the following would not:
enum wrongFoo {
bar = 'bar',
baz = 'WRONG',
}
enum wrongFoo2 {
bar = 1
}
What would be the proper syntax to make this happen?
Upvotes: 5
Views: 4733
Reputation: 1866
So, I'll add another way I found on the ol'interwebs
export enum SNIPPET_TYPES {
WIKIPEDIA = <any>"wikipedia",
GUTENBERG = <any>"gutenberg"
}
this works
function(type: SNIPPET_TYPES) {
...
type: SNIPPET_TYPES.GUTENBERG,
... (or)
if (type === SNIPPET_TYPES.GUTENBERG) ...
Upvotes: 0
Reputation: 5492
Alternative way that avoids having to declare a new type for every check:
const keyValuesMatch = <T>(kv: { [K in keyof T]: K }) => {};
enum correct {
bar = 'bar',
baz = 'baz',
}
enum incorrect {
bar = 'bar',
baz = 'wrong',
}
keyValuesMatch(correct);
keyValuesMatch(incorrect); // Type 'incorrect.baz' is not assignable to type '"baz"'.
Upvotes: 2
Reputation: 329773
If you're okay with a manual compile-time check (meaning you have to write something manually after your enum
definition), you can do this:
type EnsureCorrectEnum<T extends { [K in Exclude<keyof T, number>]: K }> = true;
And then have the compiler evaluate EnsureCorrectEnum<typeof YourEnumObjectHere>
. If it compiles, great. If not, there's a problem:
type Correct1Okay = EnsureCorrectEnum<typeof correct1>; // okay
type Correct2Okay = EnsureCorrectEnum<typeof correct2>; // okay
type WrongFooBad = EnsureCorrectEnum<typeof wrongFoo>; // error!
// ┌─────────────────────────────> ~~~~~~~~~~~~~~~
// Types of property 'baz' are incompatible.
type WrongFoo2Bad = EnsureCorrectEnum<typeof wrongFoo2>; // error!
// ┌──────────────────────────────> ~~~~~~~~~~~~~~~~
// Types of property 'bar' are incompatible.
The errors are fairly descriptive too.
Okay, hope that helps; good luck!
Upvotes: 6
Reputation: 92314
Sounds like you could use a utility we wrote. It doesn't create an enum per se, but it does create a type-safe object where the keys have their name as their values and use keyof typeof
for a bit of string-type safety.
This was created before string enums existed, which is why is called an enum, but isn't really an enum. It's just an object, but you can use it instead of hardcoding strings.
/**
* This creates a fake string enum like object. Use like so:
* const Ab = strEnum(['a', 'b']);
* type AbKeys = keyof typeof Ab;
* @param keys keys in the enum
* @returns enum object
*/
export function createStringEnum<T extends string>(keys: T[]): {[K in T]: K} {
return keys.reduce((res, key) => {
res[key] = key;
return res;
}, Object.create(null));
}
const Ab = createStringEnum(['a', 'b']);
type AbKeys = keyof typeof Ab;
const Bc = createStringEnum(['b', 'c']);
type BcKeys = keyof typeof Ab;
console.log(Bc.blah) // Compilation error blah property does not exist
// Error invalid string
const b: AbKeys = "e";
// An enum throws an error, but this isn't really an enum
// Main drawback of this approach
const a: AbKeys = Bc.b;
Even if it does not fit your needs, this could be helpful to others that aren't required to use enums.
Upvotes: 1
Reputation: 17895
Enums in Typescript are objects, so you can use Object.keys
function to get all keys in that enum and check, if they equals to theirs values. Since all keys returned with Object.keys
function are string, values must be strings too.
enum correct1 {
bar = 'bar',
baz = 'baz',
}
enum correct2 {
quux = 'quux',
}
enum wrongFoo {
bar = 'bar',
baz = 'WRONG',
}
enum wrongFoo2 {
bar = 1
}
function isEnumValid<T extends {}>(validatedEnum: T) : boolean {
return Object.keys(validatedEnum).every(k => k === validatedEnum[k]);
}
console.log(isEnumValid(correct1)); // true
console.log(isEnumValid(correct2)); // true
console.log(isEnumValid(wrongFoo)); // false
console.log(isEnumValid(wrongFoo2)); // false
Upvotes: 0