Reputation: 9285
Let's say we have a type
type Foo = {
a: string;
b: number;
c: boolean;
}
I now want to define a type for an object having a key
and a value
of a given type T
, so that the type of the value is directly inferred from the key, so that I can do:
const kv1: KeyValue<Foo> = {key: 'a', value: 'STRING'};
const kv2: KeyValue<Foo> = {key: 'b', value: 42};
const kv3: KeyValue<Foo> = {key: 'c', value: true};
If I simply do this:
type KeyValue<T> = {
key: keyof T;
value: T[keyof T]
}
... then obviously the values will be the union of all properties' values in Foo
:
And if I do this:
type KeyValue<T, K extends keyof T> = {
key: K;
value: T[K]
}
... then sure, if explicitly typed as KeyValue<Foo, 'a'>
I can create object literals that match the type of Foo
:s properties, but if I don't specifically provide the type AND the key for each literal but simply do KeyValue<Foo, keyof Foo>
, each value will be allowed to be of the type of any of the values in Foo
, i.e., all values will be string | number | boolean
instead of being inferred from the entered key
.
In the end I want to be able to do things like this:
const kvs: Array<KeyValue<Foo>> = [
{key: 'a', value: 'STRING'}, // should not compile if value is a number or boolean
{key: 'b', value: 42}, // should not compile if value is a string or boolean
{key: 'c', value: true}, // should not compile if value is a string or number
];
Is it possible to derive the KeyValue
type with these constraints, so that effectively it becomes KeyValue<Foo, 'a'> | KeyValue<Foo, 'b'> | KeyValue<Foo, 'c'>
in the second example above, without having to manually write this union? Basically I guess what I'd like is to be able to infer the type of value
from the value of key
in the current literal object.
Upvotes: 2
Views: 3644
Reputation: 1540
You cannot represent each key-value pair using the single KeyValue
object, since this would require either partial inference (for the key) or somehow referring to the key
property from value
property inside the same object.
However, what you could do is create a discriminated union using the input object.
type Foo = {
a: string;
b: number;
c: boolean;
}
type KeyValue<T> = {
[P in keyof T]: {
key: P;
value: T[P];
}
}[keyof T];
// with KeyValue<Foo>, this generates the following type
//
// {
// key: "a";
// value: string;
// } | {
// key: "b";
// value: number;
// } | {
// key: "c";
// value: boolean;
// }
const kvs: Array<KeyValue<Foo>> = [
{key: 'a', value: 40}, // ERROR
{key: 'b', value: true}, // ERROR
{key: 'c', value: "foo"}, // ERROR
];
Upvotes: 3