Consider the following:
type ValueMap = {
foo: 'FOO1' | 'FOO2';
bar: 'BAR1' | 'BAR2' | 'BAR3';
};
type Key = keyof ValueMap;
type Value<K extends Key> = ValueMap[K];
type Entry<K extends Key> = {
key: K;
value: Value<K>;
}
In other words, an Entry
consists of a key
and a value
, and the set of allowed values depends on the key, so e.g.
{key: 'foo', value: 'FOO2'} - valid
{key: 'foo', value: 'BAR3'} - invalid
{key: 'bar', value: 'BAR1'} - valid
{key: 'bar', value: 'FOO2'} - invalid
Now, I want to send a set of such entries to a function, and I want the type of each entry to be inferred from that element's key. However, If I do this as an array, this doesn't work very well:
function sendArray<K extends Key>(v: Array<Entry<K>>) {
...
}
sendArray([{key: 'foo', value: 'FOO2'}, {key: 'bar', value: 'BAR1'}])
sendArray([{key: 'foo', value: 'FOO2'}, {key: 'bar', value: 'FOO1'}]) // compiles :(
Here, sendArray
is called with K
being inferred to "foo" | "bar"
, and each array element is validated only so that key
is assignable to "foo" | "bar"
and value
is assignable to ValueMap<"foo" | "bar">
, i.e., values from foo
are valid for bar
entries, and vice versa. :(
I know that if I use a record type instead, it works:
type Entries<K extends Key> = {
[P in K]?: Value<P>;
}
function sendRecord<K extends Key>(v: EntryRecord<K>) {
}
sendRecord({foo: 'FOO2', bar: 'BAR3'})
sendRecord({foo: 'FOO2', bar: 'FOO1'}) // does not compile! :)
... which is nice, but I'd really want this to work for an array. Is there some way to do this, so that the first array element in inferred to be a Entry<"foo">
and the second element is inferred to be a Entry<"bar">
?
You don't need generics here at all, but if for some reason you still need them outside of this exact issue we will still have to make
Entry
nongeneric. The desired type is:To achieve this we will use the mapped types:
Then, in the
sendArray
we have to modify the generic to accept theEntry[]
, notKey
. And to make sure that read-only arrays are also accepted we will addreadonly
to the constraint:Testing:
playground