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

Exploring the process of dynamically updating a form based on user-selected options

I need assistance with loading an array of saved templates to be used as options in an ion-select. When an option is chosen, the form should automatically update based on the selected template. Below is the structure of my templates: export interface ...

"Enhance your software with a customizable interface or develop new functionalities to generate analogous

Having API data with a similar structure, I am looking to streamline my code by filtering it through a function. However, as someone new to TypeScript, I am struggling to implement this correctly using a function and an interface. Essentially, I aim to ach ...

What is the correct way to define the onClick event in a React component?

I have been encountering an issue while trying to implement an onClick event in React using tsx. The flexbox and button are being correctly displayed, but I am facing a problem with the onClick event in vscode. I have tried several ideas from the stack com ...

Tips for encapsulating a promise while maintaining the original return type

In my code, there is a function that utilizes an axios instance and is of type async function register(data: RegisterData): Promise<AxiosResponse<UserResponse, any>> export const register = (data: RegisterData) => api.post<UserResponse> ...

How come the information I receive when I subscribe always seems to mysteriously disappear afterwards?

I've been working on a web project using Angular, and I've run into an issue with my code that's been causing problems for a while now. The problem lies in fetching data from a server that contains translations: getTranslations(): Observab ...

What is the best way to showcase a firebase "row" containing two columns within an Ionic 2 application?

Currently in the process of developing an app to keep track of assignments using Ionic 2/Typescript and Firebase as the backend database. The main page displays a list of assignments retrieved from the database. Creating a new assignment requires the user ...

It takes a brief moment for CSS to fully load and render after a webpage has been loaded

For some reason, CSS is not rendering properly when I load a webpage that was created using React, Next.js, Material UI, and Styled-components. The website is not server-side rendered, but this issue seems similar to what's described here You can see ...

Guide on merging non-modular JavaScript files into a single file with webpack

I am trying to bundle a non-modular JS file that uses jQuery and registers a method on $.fn. This JS must be placed behind jQuery after bundling. Here is an example of the structure of this JS file: (function($){ $.fn.splitPane = ... }(JQuery) If y ...

Steer clear of utilizing the "any" type in your Express.js application built with

I have a node/express/typescript method that looks like this: // eslint-disable-next-line export const errorConverter = (err: any, req: any, res: any, next: any) => { let error = err if (!(error instanceof ApiError)) { const statusCode = e ...

Containerizing Next.js with TypeScript

Attempting to create a Docker Image of my Nextjs frontend (React) application for production, but encountering issues with TypeScript integration. Here is the Dockerfile: FROM node:14-alpine3.14 as deps RUN apk add --no-cache tini ENTRYPOINT ["/sbin ...

A method for enabling mat-spinner's entrance animation

I have recently implemented an Angular Material spinner with a disappearing animation that moves downwards before fading away. Is there a way to disable this animation? I have already tried using keyframes without success. <mat-spinner style="margin: ...

The specified type 'MutableRefObject<HTMLInputElement | undefined>' cannot be assigned to type 'LegacyRef<HTMLInputElement> | undefined'

Consider the following simplified component : const InputElement => React.forwardRef((props:any, ref) => { const handleRef = React.useRef<HTMLInputElement|undefined>() React.useImperativeHandle(ref, () => ({ setChecked(checke ...

What is the reason behind prettier's insistence on prefixing my IIAFE with ";"?

I've encountered async functions in my useEffect hooks while working on a JavaScript project that I'm currently transitioning to TypeScript: (async ():Promise<void> => { const data = await fetchData() setData(data) })() Previously, ...

Dealing with Error TS2769 in Visual Studio Code when passing props to a custom component in Vue 2 with Typescript

I've encountered an issue with a Vue JS component that involves passing a custom prop. I am utilizing the Vue Options API without utilizing the class component syntax. Whenever I pass ANY prop to my custom component the-header, I receive an error sta ...

Angular FormControl is a built-in class that belongs to the Angular forms module. It

I’ve been working on adjusting tslint for the proper return type, and here’s what I have so far: get formControls(): any { return this.form.controls; } However, I encountered an error stating Type declaration of 'any' loses type-safet ...

Splitting a string in Typescript based on regex group that identifies digits from the end

Looking to separate a string in a specific format - text = "a bunch of words 22 minutes ago some additional text". Only interested in the portion before the digits, like "a bunch of words". The string may contain 'minute', & ...

Guide to modifying text color in a disabled Material-UI TextField | Material-UI version 5

How can I change the font color of a disabled MUI TextField to black for better visibility? See below for the code snippet: <TextField fullWidth variant="standard" size="small" id="id" name=&quo ...

Unable to retrieve shared schema from a different schema.graphql file within the context of schema stitching

In my project, I have a user schema defined in a file named userSchema.graphql; id: String! userName: String! email: String! password: String! } In addition to the user schema, I also have separate schema files for login and register functionalit ...

What is the correct way to invoke a function from a separate file in typescript?

I am new to typescript and still learning. I have a question regarding calling a function defined in file B from file A. Can someone guide me on how to achieve this? ...

A Unique Identifier in Kotlin

In my typescript class, I have a member that accepts any as the name: interface ControlTagType { type?: String | null; [name: string]: any } class ControlTag { tagSource: String | null = null; tag: ControlTagType | null = null; } expor ...