Link to typescript playground
The utility type converting a union to an intersection was fundamental: I've taken it from here
type Union =
| {
name: "a";
event:
| { eventName: "a1"; payload: string }
| { eventName: "a2"; payload: number };
}
| {
name: "b";
event: { eventName: "b1"; payload: boolean };
};
type nested = {
[n in Union['name']]: {
[e in Extract<Union, { name: n }>['event']['eventName']]: {
name: n,
eventName: e,
payload: Extract<Union['event'], {eventName: e}>['payload']
}
}
}
// https://stackoverflow.com/a/50375286/3370341
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type r = UnionToIntersection<nested[keyof nested]>
type Result = r[keyof r]
type Expected =
| { name: "a"; eventName: "a1"; payload: string }
| { name: "a"; eventName: "a2"; payload: number }
| { name: "b"; eventName: "b1"; payload: boolean };
declare const expected: Expected;
declare const result: Result;
// In case types are not the same I expect a type error here
const y: Result = expected
const l: Expected = result
I wonder if there is an easier way.
I've started by creating a nested object type, but with the aim of putting name and eventName on the same level.
The intermediate result was something like this:
type nested = {
a: {
a1: {
name: 'a',
eventName: 'a1',
payload: string
},
a2: {
name: 'a',
eventName: 'a2',
payload: number
}
},
b: {
b1: {
name: 'b',
eventName: 'b1',
payload: boolean
}
}
}
Then I've extracted a union of only the inner values types.
I've tried using things like Extract on eventName, but while it works for 'b1', it leads to never with 'a1' and 'a2'.
type b1 = Extract<Union, {event: {eventName: 'b1'}}> // ok
type a1 = Extract<Union, {event: {eventName: 'a1'}}> // never