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