DEV Community

Acid Coder
Acid Coder

Posted on • Edited on

Typescript WTF Moment 12: Beware of Object Literal Unions

type ABC = { A: number, B: number } | { C: number }

const abc: ABC = { A: 1, B: 2, C: 3 } // no error!
Enter fullscreen mode Exit fullscreen mode

Image description

playground

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!
Enter fullscreen mode Exit fullscreen mode

Image description

playground

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!
Enter fullscreen mode Exit fullscreen mode

Image description

playground

The purpose of T extends T is to distribute unions

credit

Top comments (0)