Typescript WTF Moment 12: Beware of Object Literal Unions
2 min readJun 20, 2023
type ABC = { A: number, B: number } | { C: number }
const abc: ABC = { A: 1, B: 2, C: 3 } // no error!
Typescript excess property check allow any property from unions of object literal
Solutions
Assign type `never` to excess properties and make them optional
type ABC = { A: number, B: number, C?: never } | { A?: never, B?: never, C: number }
const abc: ABC = { A: 1, B: 2, C: 3 } // error!
const ab: ABC = { A: 1, B: 2 } // ok!
const c: ABC = { C: 3 } // ok!
const bc: ABC = { B: 2, C: 3 } // error!
With common properties that have different types, Typescript is able to discriminate unions
However it is a lot of work if we have a large unions of object literal
We need to create an utility type that automate this process
type GetAllKeys<T> = T extends T ? keyof T : never
type CreateOptionalExcessProperty<T, Y> = T extends T ? T & Partial<Record<Exclude<GetAllKeys<Y>, keyof T>, never>> : never
type StrictUnion<T> = CreateOptionalExcessProperty<T, T>
type ABC = StrictUnion<{ A: number, B: number } | { C: number }>;
const abc: ABC = { A: 1, B: 2, C: 3 } // error!
const ab: ABC = { A: 1, B: 2 } // ok!
const c: ABC = { C: 3 } // ok!
const bc: ABC = { B: 2, C: 3 } // error!
The purpose of `T extends T` is to distribute unions