what's wrong with the way I define this type
type Payload<Es> = {
trigger:
Es extends Record<infer K, infer V>
? { key: K, data: V } : never
}
type Evs = {
"item.1": { ok: "Y", code: number }
"item.2": 1 | 0
}
const payload: Payload<Evs> = {
trigger: {
key: "item.1",
data: 1
}
}
payload.trigger.data
doesn't get inferred as I expect
expected
{ ok: "Y", code: number }
what I get
0 | { ok: "Y", code: number } | 1
The Record<K, V>
utility type is a type corresponding to objects with property keys of type K
and property values of type V
. But specific keys are not associate with specific values. If you start with {a: string, b: number}
and try to infer a Record<K, V>
from it, you'll get Record<"a" | "b", string | number>
. That's the same type you'd get from {a: number, b: string}
. So you really don't want to infer to a Record
at all here, unless you want to mix all the properties together.
Instead you really need to just map over the type, iterating over each key K
, and producing the corresponding object type, and then index into the mapped type to get a union of properties. That is, you want a distributive object type (as coined in microsoft/TypeScript#47109):
type Payload<E> = {
trigger: { [K in keyof E]: { key: K, data: E[K] } }[keyof E]
}
You can verify that this gives you what you're looking for:
type Evs = {
"item.1": { ok: "Y", code: number }
"item.2": 1 | 0
}
type PEvs = Payload<Evs>;
/* type PEvs = {
trigger: {
key: "item.1";
data: {
ok: "Y";
code: number;
};
} | {
key: "item.2";
data: 0 | 1;
};
} */
const payload: Payload<Evs> = {
trigger: {
key: "item.1",
data: 1 // error!
}
}