Reputation: 8846
I am creating a user defined type guard to determine if an (unsafe) string fetched at runtime is a member of a string enum
:
enum Vendor {
ACME = "Acme Co.",
FOOBAR = "Foo Bar Industries"
}
export let isVendor = (x: string | Vendor): x is Vendor => {
return !!Vendor[x];
};
When I attempt to compile the code above, I receive the following:
[ts] Element implicitly has an 'any' type because expression is not of type 'number'.
(parameter) x: string
One possible fix is as follows:
export let isVendor = (x: any): x is Vendor => {
return !!Vendor[x];
};
...but I prefer to avoid any
when I know the type is atleast a string
.
I can avoid using any
by changing the type signature of x
to number | string | Vendor
, but this is still less than ideal.
Is there any way to perform square bracket access on an enum
using a string?
Upvotes: 2
Views: 1318
Reputation: 13198
I do get that error when I have 'no implicit any' turned on. I suspect this may be a compiler issue.
It works if you just cast x
to any
:
enum Vendor {
A = "a",
B = "b"
}
const isVendor = (x: string | Vendor): x is Vendor => {
return !!Vendor[<any>x];
};
console.info(isVendor("A")); // true
console.info(isVendor("a")); // false
console.info(isVendor("C")); // false
I notice that in examples of this feature, they either have similar casts, or else the parameter is of type any
.
Also check here for some explanation of this issue.
I still think you need to decide if you're looking for names or values, though. Based on your implementation, I think you might really be looking for isVendorValue
rather than isVendorName
. (I don't think it even makes sense to ask if the name is a Vendor
, because it isn't; you want to know if the value is a Vendor
.):
enum Vendor {
A = "a",
B = "b"
}
// doesn't make sense. the name cannot be a Vendor.
const isVendorName = (name: string | Vendor): name is Vendor => {
return !!Vendor[<any>name];
};
// requires ES7, see below
const isVendorValue = (value: string | Vendor): value is Vendor => {
return Object.values(Vendor).some((x: any) => x === value);
};
console.info(isVendorName("A")); // true
console.info(isVendorName("a")); // false
console.info(isVendorName("C")); // false
console.info(isVendorName(Vendor.A)); // false
console.info(isVendorValue("A")); // false
console.info(isVendorValue("a")); // true
console.info(isVendorValue("C")); // false
console.info(isVendorValue(Vendor.A)); // true
Object.values
is apparently ES7 so here's an alternative implementation. Since the values wouldn't change at runtime you could probably benefit from caching the values, too.
const isVendorValue = (value: string | Vendor): value is Vendor => {
return Object
.keys(Vendor)
.map((key: any) => Vendor[key])
.some((x: any) => x === value);
};
Another thing I would suggest now that I think of it is that contrary to your suggestion, the parameter should probably actually be of type any
anyway. You say "I know the type is at least a string
" but that just needlessly restricts the usage of this method to testing strings.
And you don't really know that, depending where you use the method. What you know is that if it's not a string, then it's definitely not a Vendor
.
The idea is, given some object or value (string
or not), determine if it is a member of this enum
, and if it is then treat it as one in the type system. A more broadly applicable method is better than an unnecessarily narrow one.
const isVendorValue = (value: any): value is Vendor => {
return typeof value === "string" &&
Object
.keys(Vendor)
.map((key: any) => Vendor[key])
.some((x: any) => x === value);
};
Upvotes: 3