In Typescript, it is possible to create record types with string enums:
enum AxisLabel { X = "X", Y = "Y" }
export const labelLookup: Record<AxisLabel, string> = {
[AxisLabel.X]: "X axis",
[AxisLabel.Y]: "Y Axis"
};
I need to create a Record
object similar to the one above, but I do not wish to use a string enum.
When I try something like this:
enum AxisLabel { X, Y }
export const labelLookup: Record<AxisLabel, string> = {
[AxisLabel.X]: "X axis",
[AxisLabel.Y]: "Y Axis"
};
Typescript produces the following error:
Type 'AxisLabel' does not satisfy the constraint 'string'.
It is possible to create JS objects with both numbers and strings as their member names.
I wish to do the same in Typescript, but without resorting to unsafe coercion or type casts. How do I do create numeric enum Record<> types in Typescript without using string enums, any
or type casts?
Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript. Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based 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.
An enum is usually selected specifically because it is immutable - i.e. you would never want the values to change as it would make your application unpredictable.
UPDATE: TypeScript 2.9 added support for number
and symbol
as valid key types, thus the code above no longer gives an error, and this answer is no longer necessary, as long as you're using TypeScript 2.9 or above.
For TypeScript 2.8 and below:
Object keys in JavaScript are, believe it or not, always strings (okay, or Symbol
s), even for arrays. When you pass a non-string value as a key, it gets coerced into a string first. But of course people expect numeric keys to make sense, especially for arrays. TypeScript kind of reflects this inconsistent philosophy: usually you can only specify string-valued keys (such as in mapped types like Record<K,V>
). When those situations interact you get weirdness.
Here's one thing I've sometimes done: explicitly represent the coercion from number to string with the following tuple type:
export type NumericStrings = ["0","1","2","3","4","5","6","7","8","9","10"] // etc
Note that you can extend that as long as you need it. Then, you can use lookup types to convert a numeric type to its string counterpart for use in mapped types. Like so:
export enum AxisLabel { X, Y }
// note the key is NumericStrings[AxisLabel], not AxisLabel
export const labelLookup: Record<NumericStrings[AxisLabel], string> = {
[AxisLabel.X]: "X axis",
[AxisLabel.Y]: "Y Axis"
};
That works without error. If you inspect the type of labelLookup
, it shows up as Record<"0" | "1", string>
. When you try to index into labelLookup
, the following mostly-expected things happen:
labelLookup[AxisLabel.X]; // okay
labelLookup[0]; // okay
labelLookup["0"]; // also okay
labelLookup[10]; // error
labelLookup.X; //error
Hope that helps; good luck!
The definition of record is very specific that the key must be assignable to string, so there is no way to use Record
with a number, more generally the key in a mapped type must be a string.
You can use a regular index signature, but it you cannot restrict an index signature to anything but string or number (according to the language spec) which means that any number would be valid not just the enum values:
export const labelLookup: { [index: number]: string } = {
[AxisLabel.X]: "X axis",
[AxisLabel.Y]: "Y Axis",
[3] = "" // Also works but you don't want that
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With