Combine multiple objects to create a new object that represents the intersection of all properties

Imagine you have these three objects:

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

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

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

A simple merge function has been written to combine these three objects into one:

// Not the best solution as it only takes 3 parameters
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;
};

// Works fine. Provides expected return type and autocompletion in IDE for properties "foo", "name", and "fizz"
const test1 = merge(obj, obj2, obj3);

However, there are two issues with this approach:

  1. It currently only supports merging 3 objects. I would like it to support an arbitrary number of objects.
  2. The generic declaration is overly lengthy.

The function's return type (visible when hovering over test1) is an intersection of all inferred types from the 3 objects passed in. While this is great, the method could be improved.

An attempt was made to modify the function:

// Trying to accept any number of objects rather than just 3
const merge2 = <T extends Record<string, unknown>[]>(...rest: T) => {
    // Combining all objects
    return rest.reduce((acc, curr) => ({ ...acc, ...curr }), {});
};

// The function works but the type of test2 ends up being "Record<string, unknown>". Type inference for the objects 
// doesn't capture specific properties such as "name", "foo", and "fizz"
const test2 = merge2(obj, obj2, obj3)

Although the function remains the same, efforts were made to allow for multiple objects to be merged. However, the return type always defaults to Record<string, unknown>, resulting in loss of type information from the passed objects. This means that trying to access test2.f for autocomplete on foo won't work.

How can the object types be preserved? And how can an intersection across N number of types be achieved?

Answer №1

Check out this innovative solution that harnesses the power of the UnionToIntersection type found 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 }), {});
};

The generic type T is used to collect all input types as elements in an array called args. By using T[number], we create a union of all elements in the array T, then transforming it into an intersection with the UnionToIntersection type, which becomes the function's return type.

Let's put it to the test:

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

Try it out on the Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to include extra data in Angular Firebase user creation using the createUserWithEmailAndPassword

Currently, I am working on implementing the Firebase createUserWithEmailAndPassword method. However, I would like to include an additional field named 'name' in Cloud Firestore. Below is a snippet of my code: auth.service.ts SignUp(email: string ...

"Embracing the power of multiple inheritance with Types

I am struggling with the concept of multiple inheritance in TypeScript. It doesn't make sense to overload a hierarchy with too much functionality. I have a base class and several branches in the hierarchy. However, I need to utilize mixins to isolate ...

Enhancing Styled Components in Material-UI with custom props and themes using Typescript

I am exploring the use of the Material UI styled components API to incorporate both a custom theme and some props into a specific custom element. Although I have managed to get either a custom theme or props working individually, I am struggling to make t ...

The CORS policy specified in next.config.js does not appear to be taking effect for the API request

I am currently working on a Next.js application with the following structure: . ├── next.config.js └── src / └── app/ ├── page.tsx └── getYoutubeTranscript/ └── getYoutubeTranscript.tsx T ...

What is the local date format for the Ionic DatePicker?

I have successfully implemented a DatePicker in my Ionic Project, but the date is displaying in the wrong time format. Here is my function: showDatePicker(){ this.datePicker.show({ date: new Date(), mode: 'date', allowOldDates: fal ...

The process of linking a Json response to a table

In my products.components.ts class, I am retrieving Json data into the variable this.Getdata. ngOnInit() { this._service.getProducts(this.baseUrl) .subscribe(data => { this.Getdata=data this.products=data alert(JSON.stringify(this.Getdata)); ...

Optimizing File Transfers and Streaming Using Next.js and CDN Integration

As I work on developing a download system for large files on my website using Next.js and hosting the files on a CDN, I face the challenge of downloading multiple files from the CDN, creating a zip archive, and sending it to the client. Currently, I have i ...

Utilizing UseRef in Typescript for an Idle Timer

Feeling frustrated with Typescript, everything seems unnecessarily complicated. Trying to follow a tutorial on react-idle-timer and encountering TypeScript compilation issues within minutes. The guides online suggest using when calling useRef with TypeS ...

Creating a null array of a specific size can easily be accomplished in Typescript

When I use the splice method to add elements to an array at a specified index, I find myself creating a null array first in order to achieve this. If I use an empty array instead, the elements do not get pushed to the specific instance that I intended. Cur ...

Is it possible to define a new type in TypeScript using "runtime" keys?

Illustrate with an example: class ModuleOptions { key1?: string; key2?: string; keyA?: string; keyB?: string; } class Module { static options: ModuleOptions = { key1: 'key1', key2: 'key2', keyA: 'keyA&apos ...

Error TS2322: You cannot assign a Promise<any> to a string type

Having an issue in my react app where I am attempting to import the img source but encountering an error: TS2322: Type 'Promise<any>' is not assignable to type 'string'. What is the correct way to import an element into a variabl ...

Is there an alternative course of action since determining if Observable is empty is not feasible?

I am diving into Angular 11 and exploring the world of Observables and Subjects as a beginner. Within my application, I have a mat-autocomplete component that organizes its results into categories. One of these categories is dedicated to articles, and I&a ...

"Error in Visual Studio: Identical global identifier found in Typescript code

I'm in the process of setting up a visual studio solution using angular 2. Initially, I'm creating the basic program outlined in this tutorial: https://angular.io/docs/ts/latest/guide/setup.html These are the three TS files that have been genera ...

Anticipated outcome is the need to provide a value upon completion of arrow function recursion (consistent-return)

If I have a recursive function like the example provided, is it possible to simply return false after the recursive foo call? function foo(fn, redo, interval = 1000) { return new Promise( (resolve, reject) => { fn() .then(resolve) ...

encountering the issue of not being able to assign a parameter of type 'string | undefined' to a parameter of type

Seeking help with the following issue: "Argument of type 'string | undefined' is not assignable to parameter of type" I am unsure how to resolve this error. Here is the section of code where it occurs: export interface IDropDown { l ...

a callback may seem like it's not a function, but in reality, it is

This question might seem simple, but I'm struggling to grasp the concept of callbacks, especially in NodeJS. My issue arises when trying to retrieve data from MySQL, something that is usually straightforward in most programming languages: In my rout ...

There was an error in calling `prisma.user.findUnique()`:

Here is my code snippet for the API route: export const POST = async (req: NextRequest) => { ... try { const { email, name, password } = await req.json(); console.info(email, name, password); const existingUser = await prismadb.user.findUn ...

Unable to execute dockerfile on local machine

I'm currently attempting to run a Dockerfile locally for a Node TypeScript project. Dockerfile FROM node:20-alpine EXPOSE 5000 MAINTAINER Some Dev RUN mkdir /app WORKDIR /app COPY ./backend/* /app RUN npm i CMD ["npm","start"] However, I encoun ...

Creating an HTML tag from Angular using TypeScript

Looking at the Angular TypeScript code below, I am trying to reference the divisions mentioned in the HTML code posted below using document.getElementById. However, the log statement results in null. Could you please advise on the correct way to reference ...

Leveraging import and export functionality in TypeScript while utilizing RequireJS as a dependency

I am in the process of transitioning a complex JavaScript application from Backbone/Marionette to TypeScript. While making this shift, I want to explore the benefits of exporting and importing classes using files as modules. Is it necessary to incorporat ...