How do I get the union type of all key-value pairs in an object in TypeScript?

Issue

Let’s say I have an object like this:

const person = {
  id: 87
  name: 'Some Name',
  address: {
    street: 'Some street',
    country: 'Some country'
  }
}

I want to get a type that is the union of all key value pairs. So the type should be:

{ id: number } | { name: string } | { address: { street: string; country: string; } }

How can that be done? I tried this:

type PersonInfo = {
  id: number;
  name: string;
  address: {
    street: string;
    country: string;
  }
}

type PersonMap<M extends { [index: string]: any }> = {
  [Key in keyof M]: M[Key]
};

type PersonTest1 = PersonMap<PersonInfo>[keyof PersonMap<PersonInfo>];
// Returns "number | string | { street: string; country: string; }"

type PersonTest2 = PersonMap<PersonInfo>;
// Returns { id: number; name: string; address: { street: string; country: string;} }

How can I get the union type described above?

Solution

It seems like you want a type function of the form UnionOfSingleKeyObjects<T> which turns an object type T into a union of single-key object types where each key in keyof T appears exactly once. Depending on your use cases, you could define such a type function like this:

type UnionOfSingleKeyObjects<T extends object> = 
  { [K in keyof T]-?: { [P in K]: T[P] } }[keyof T]

And verify that it works for PersonInfo as desired:

type PersonKVPairs = UnionOfSingleKeyObjects<PersonInfo>
/* type PersonKVPairs = {
    id: number;
} | {
    name: string;
} | {
    address: {
        street: string;
        country: string;
    };
} */

The definition of UnionOfSingleKeyObjects is a mapped type where we iterate over each key type K in keyof T, calculate the single-key object in question for each key, and then index into it with keyof T to get a union of all single-key object types.

Instead of indexing into a mapped type, you could use distributive conditional types to get the same effect:

type UnionOfSingleKeyObjects<T extends object> =
    keyof T extends infer K ? K extends keyof T ?
    { [P in K]: T[P] } : never : never

Either way works; I tend to use mapped types because they are a bit easier to explain than conditional type distributivity.


In both of those approaches, the single-key object with key K is written as {[P in K]: T[P] }. This can alternatively be written as Record<K, T[K]> using the Record<K, V> utility type, or as Pick<T, K> using the Pick<T, K> utility type. These other versions have their pros and cons, and may change how the type appears in IntelliSense quickinfo as well as whether or not optional/readonly keys stay optional/readonly in the output. If you care about preserving these modifiers and don’t want to see Pick or Record in your quickinfo, you can write it like {[P in keyof Pick<T, K>]: T[P]}, like this:

type UnionOfSingleKeyObjects<T extends object> =
    { [K in keyof T]-?: { [P in keyof Pick<T, K>]: T[P] } }[keyof T]

and we can see that such modifiers are preserved:

type Example = UnionOfSingleKeyObjects<{ a?: string, readonly b: number }>
/* type Example = {
    a?: string;
} | {
    readonly b: number;
} */

Again, depending on your use cases, you may or may not care about such things.

Playground link to code

Answered By – jcalz

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published