Home > OS >  Merge N number of objects and get intersection type with all properties
Merge N number of objects and get intersection type with all properties

Time:06-13

Consider these three objects:

const obj = {
    name: 'bob',
};

const obj2 = {
    foo: 'bar',
};

const obj3 = {
    fizz: 'buzz',
};

I've written a simple merge function that merges these three objects into one:

// suboptimal solution. Only takes in 3 params
const merge = <A extends Record<string, unknown>, B extends Record<string, unknown>, C extends Record<string, unknown>>(...rest: [A, B, C]) => {
    return rest.reduce((acc, curr) => ({ ...acc, ...curr }), {}) as A & B & C;
};

// Totally works. IDE TypeScript support autocomplete works and the return type is exactly as
// expected. test1 type has properties of "foo", "name", and "fizz"
const test1 = merge(obj, obj2, obj3);

There are two problems with this:

  1. It only supports 3 objects at the moment. I want to support N number of objects.
  2. The generic is freaking huge

The return type of this function (observable by hovering over test1) is an intersection of all the inferred types of the 3 objects passed in. Beautiful - exactly what I'm looking for, but the solution is still suboptimal.

I've tried doing this:

// Attempting to take in N number of objects instead of just supporting 3
const merge2 = <T extends Record<string, unknown>[]>(...rest: T) => {
    // Merging them all
    return rest.reduce((acc, curr) => ({ ...acc, ...curr }), {});
};

// Function obviously works, but test2 type is just "Record<string, unknown>". TypeScript doesn't seem to
// know that test2 has "name", "foo", and "fizz" properties
const test2 = merge2(obj, obj2, obj3)

The function of course is the same, but instead I'm trying to accept an arbitrary number of objects. The problem is that the return type the function is always Record<string, unknown>, so the type inferences for the 3 (or more) objects passed in is totally lost. Therefore, you can't do test2.f and get autocomplete for foo.

How can I avoid losing the object types? And how can I created an intersection between N number of types?

CodePudding user response:

Here is a solution that uses the UnionToIntersection type from here.

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

const merge = <
  T extends any[]
>(...rest: T): UnionToIntersection<T[number]> => {    
    return rest.reduce((acc, curr) => ({ ...acc, ...curr }), {});
};

I used the generic type T to store all the types passed as args in an array. Using T[number] we can get a union of all elements in the array T. With the UnionToIntersection type, we convert this union to an intersection which will be the return type of the function.

Let's see if it works:

const test = merge(obj, obj2, obj3)
// const test: {
//     name: string;
// } & {
//     foo: string;
// } & {
//     fizz: string;
// }

Playground

  • Related