Issue
In TypeScript you can do
const obj = {
'123-123': 1,
'456-456': 2,
}
type objKeys = keyof typeof obj
And objKeys
is a union type '123-123' | '456-456'
.
Is something similar possible if obj
was not an object, but a Map?
const map = new Map<string, number>([
['123-123', 1],
['456-456', 2],
])
type mapKeys = ???
Solution
If you write new Map<string, number>(...)
then you are explicitly giving the Map
a string
key type, which means it will allow you to set()
and get()
and string-valued key. If you want to constrain the keys to a particular union of string literal types then you will need to change how you construct the Map
. For example, if you define MapKeys
ahead of time, you can use it instead of string
:
type MapKeys = '123-123' | '456-456';
const map = new Map<MapKeys, number>([
['123-123', 1],
['456-456', 2],
]);
But this is a bit redundant, and backwards from what you're asking for anyway. You want the compiler to infer MapKeys
from the arguments passed to the Map
constructor.
Since you said that the use case is for the Map
to be constant (so not only are its keys constrained, but its values also do not change), then you might be better served by the ReadonlyMap<K, V>
interface provided by TypeScript. There is no ReadonlyMap
at runtime, but the TypeScript compiler will allow you to assign a Map
instance to a variable of type ReadonlyMap
.
And if we want the compiler to infer MapKeys
, we might want to constrain the constructor's key type to string
, which gives the compiler a hint that strings.
Both of these imply that a helper function to create new ReadonlyMap<K, V>
where K
is constrained to string
will help:
function ReadonlyMapWithStringKeys<K extends string, V>(
iterable: Iterable<[K, V]>): ReadonlyMap<K, V> {
return new Map(iterable)
}
const map = ReadonlyMapWithStringKeys([
['123-123', 1],
['456-456', 2],
])
// const map: ReadonlyMap<"123-123" | "456-456", number>
Now we can see that map
is of a type whose keys are strongly typed. We can now define MapKeys
from it. There are multiple ways to do it; one is using conditional type inference with the infer
keyword
type MapKeys = typeof map extends ReadonlyMap<infer K, any> ? K : never;
// type MapKeys = "123-123" | "456-456"
Or you could use utility types like the Parameters<T>
type to ask the compiler what the first parameter of map.get()
is:
type MapKeys = Parameters<typeof map["get"]>[0]
// type MapKeys = "123-123" | "456-456"
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.