Home > Back-end >  How to discriminate union type by checking any object key in the union in TypeScript?
How to discriminate union type by checking any object key in the union in TypeScript?

Time:12-27

I have a type that looks like this.

type Focus =
    | { sidebar: true }
    | { tabbar: true }
    | { menu: string }

The whole point of this type structure is the convenience of being able to do things like if (focus.sidebar) or if (focus.menu == "open") so I don't want to rewrite my code as if ("sidebar" in focus) and if ("menu" in focus && focus.menu === "open").

This type would technically work for me:

type Focus =
    | { sidebar: true; tabbar?: undefined; menu?: undefined }
    | { tabbar: true; sidebar?: undefined; menu?: undefined }
    | { menu: string; sidebar?: undefined; tabbar?: undefined }

But this is really inconvenient to write.

I've made it this far:

type PartialEmpty<T> = { [A in keyof T]?: undefined }

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never

type Focus2 = PartialEmpty<UnionToIntersection<Focus>>
// {
//     sidebar?: undefined;
//     tabbar?: undefined;
//     menu?: undefined;
// }

But I can't figure out how to intersect and distribute that type over the union... This is pretty close:

type Distribute<T> = T extends any ? Omit<T, keyof Focus2> & Focus2 : never

type Focus3 = Distribute<Focus>

But it doesn't quite work...

const focus: Focus3 = {} as any
if (focus.menu) {
    focus.m
}

Playground Link

CodePudding user response:

T in Distribute<T> is one of:

| { sidebar: true }
| { tabbar: true }
| { menu: string }

So you need to omit keyof T from Focus2 and intersect with T:

type Distribute<T> = T extends any ? Omit<Focus2, keyof T> & T : never

Playground

  • Related