Skip to content

[Proposal] Tagged Union To Intersection #173

@mohaalak

Description

@mohaalak

Problem

let's say we have these types

type Email = {id: number, type: "Email", email: string}
type Phone = {id: number, type: "Phone", phone: string}

type ContactInfo= Email | Phone

and we want to send it to an API or save it do database but the API accepts something like this

interface ContactInfoToSave {
  id: number;
  type: "Email" | "Phone";
  phone?: string;
  email?: string;
}

UnionToIntersecion doesnot work

if we use UnionToIntersection we get never cause the type cannot be merged to one it's like when you want to intersect UnionToIntersection<string | number> it cannot be done.

Solution

I have a solution for it

 type DistributiveOmit<T, K extends keyof T> = T extends any ? Omit<T, K> : never;

export type TaggedUnionToIntersection<T> = UnionToIntersection<
  Partial<DistributiveOmit<T, keyof T>> & Pick<T, keyof T>
>;

Let me explain how does it work.
The default Omit and Pick utility type does not go inside each union type, but it's going to pick only the common type that available in every union type. also keyof works exactly like that only will export the keys is common in each union;

so

type K = keyof ContactInfo; // the only common between Email and Phone is => "type" | "id"

now if we pick these two keys

type Picked = Pick<ContactInfo, keyof ContactInfo>; // {type: "Email" | "Phone", id: number}

Now we want to omit id and type but we want to go inside each union, so we can remove it. for this we can use Distributive conditional types hence the DistributiveOmit.

After that we got something like

type Example = {id: number, type: "Email" | "Phone"} & ({phone?: string} | {email?: string})

now with UnionToIntersection we can merge all of this together and make the ContactInfoToSave

I did not pull request because I thought let's discuss it a little about it.
like if the TaggedUnionToIntersection is a good name or if we want to no common between union be Partial or not.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions