Combining two objects/interfaces in a deep merging process, where they do not intersect, can result in a final output that does not

When attempting to merge two objects/interfaces that inherit from the same Base interface, and then use the result in a generic parameter constrained by Base, I encounter some challenges.

// please be patient
type ComplexDeepMerge<T, U> = {
  [K in (keyof T | keyof U)]:
  K extends keyof U ?
  K extends keyof T ?
  T[K] extends Record<string, unknown> ?
  U[K] extends Record<string, unknown> ?
  ComplexDeepMerge<T[K], U[K]> :
  U[K] :
  U[K] :
  U[K] :
  K extends keyof T ?
  T[K] :
  never
}

interface Base {
  name: string;
}
type DoSomething<T extends Base> = T;

// The type 'ComplexDeepMerge<T, U>' does not meet the constraint of 'Base'.
type Testing<T extends Base, U extends Base> = DoSomething<ComplexDeepMerge<T, U>>;

interface Base { name: string; } type DoSomething<T extends Base> = T; type Test<T extends Base, U extends Base> = DoSomething<T & U>; type tester = Test<Base & { age: string; }, Base & { age: number; }>["age"] // ^? never // I need this to be `number`, not `never` // View more details here: https://stackoverflow.com/a/57201439

playground

Answer №1

When defining UglyDeepMerge<T, U>, the intention is for the result to always be assignable to U, regardless of what type U represents. If U is a generic type constrained to Base, then UglyDeepMerge<T, U> will also be constrained to Base. Unfortunately, due to the complexity of the conditional type used in the implementation of UglyDeepMerge<T, U>, the compiler may struggle to analyze it properly. Generic conditional types like this are often opaque to the compiler, which means their evaluation is deferred. The compiler can determine that the result is constrained to Base when specific types for T and U are provided, but making the leap from individual cases to the more abstract generic case is beyond its capabilities at this time.

To help the compiler understand that UglyDeepMerge<T, U> is indeed assignable to U for any given U, one possible solution is to refactor the definition by wrapping it with Extract<⋯, U>:

type UglyDeepMerge<T, U> = Extract<{
  [K in (keyof T | keyof U)]:
  K extends keyof U ? K extends keyof T ? 
  T[K] extends Record<string, unknown> ? U[K] extends Record<string, unknown> ?
  UglyDeepMerge<T[K], U[K]> :
  U[K] : U[K] : U[K] :
  K extends keyof T ? T[K] : never
}, U>

type Test<T extends Base, U extends Base> = 
  DoSomething<UglyDeepMerge<T, U>>; // okay

This approach works because the Extract<T, U> utility type is designed as a simple conditional type where the compiler can easily see that the result will be assignable to both T and U. Another option is to simplify the definition further into something more direct that showcases clear assignability, such as using an intersection type:

type DeepMerge<T, U> = (
  T extends object ? {
    [K in keyof T]: K extends keyof U ? DeepMerge<T[K], U[K]> : T[K]
  } : unknown
) & U;

type Test<T extends Base, U extends Base> = 
  DoSomething<DeepMerge<T, U>>;

This second method involves intersections and may not fit your exact requirements, but it provides a more straightforward path for the compiler to recognize the assignability. Experimenting with different approaches can help find the best solution for your specific use case.

https://www.typescriptlang.org/play?ssl=17&ssc=71&pln=16&pc=1#code/JYOwLgpgTgZghgYwgAgEJwM4oN4ChnIhwC2EAXMhmFKAOYDcuAvrmAJ4AOKAIgPYDKvUmAAWdADwAVZBAAekEABMMaTBAB8yALzJJjXLiKkMHRCgDqvKAGsVeAuy7IAqrQA2bbhAgcAstFoIKQAaF00dAFF5KEQwcXsCZABtAGlkUGQACmsINl4YXWQAH2QcvILnAEoAXTJ8RLS5BWVS3PyXZAB+ZEb5CCUVMvbpTvqCSVTqmT6B5AAlCAQrRXEqGhBaUIBXEGsQXgB3EE1u50np5pUFpagVtbpt3f2jk7GXd09vPwCgi...manner

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

Trouble arises when managing click events within the Material UI Menu component

I've implemented the Menu Component from Material UI as shown below - <Menu open={open} id={id} onClose={handleClose} onClick={handleClick} anchorEl={anchorEl} transformOrigin={{ horizontal: transformOriginRight, vertical: t ...

I have to make sure not to input any letters on my digipas device

There is a slight issue I am facing. Whenever I input a new transfer of 269 euros with the bank account number BE072750044-35066, a confirmation code is required. The code to be entered is 350269. https://i.stack.imgur.com/YVkPc.png The digits 350 corres ...

Display Material Popup in Angular before user leaves the page

My goal is to display an Angular Material Dialog Box (Popup window) when the user clicks the Chrome Window Close button. The Dialog modal should prompt the user if they want to save changes or cancel. However, the modal only appears for a brief moment and ...

Having trouble with Nextjs API Integration - encountering error 404

I'm currently facing a major issue and I've hit a dead end. I've been spending days trying to connect my local nextjs 14 app to the CVENT API, but I keep receiving a persistent 404 error. Here's what is displayed in the frontend console ...

Issues with Router navigation in an Ionic-Angular application

I am currently working on a straightforward application using Angular 9 and Ionic 5. The main page consists of a list of items. Here is my HTML code: <ion-header> <ion-toolbar> <ion-title>recipes</ion-title> </ion-toolba ...

Obtain the data from a service that utilizes observables in conjunction with the Angular Google Maps API

In my Angular project, I needed to include a map component. I integrated the Google Maps API service in a file called map.service.ts. My goal was to draw circles (polygons) on the map and send values to the backend. To achieve this, I added event listeners ...

The cancel function in lodash's debounce feature does not successfully halt the execution of the

In my angular application, I have implemented http calls on each modelChange event with the help of lodash's _.debounce(). However, I'm facing an issue where I am unable to cancel these calls after the initial successful execution of debounce. ...

Running unit tests using Typescript (excluding AngularJs) can be accomplished by incorporating Jasmine and Webpack

While there is an abundance of resources on how to unit test with Webpack and Jasmine for Angular projects, I am working on a project that utilizes 'plain' TypeScript instead of AngularJs. I have TypeScript classes in my project but do not use c ...

Could a tslint rule be implemented in Typescript classes to ensure method return types are enforced?

After diving into the tslint rules here, it seems that although the typedef rule's call-signature option might be close to what I need, it doesn't address the absence of a return type. Is there a specific rule (if one exists) that can enforce re ...

Combining Multiple .ts Files into a Single File: A Simplified Application Structure with TypeScript 1.8

Currently, I am in the process of developing an Electron application and I have decided to implement TypeScript for this project. While TypeScript essentially boils down to JavaScript in the end, my familiarity with it makes the transition seamless. As of ...

Ways to invoke a function in HTML aside from using the (click)="function()" syntax

How can I display data from a GET request to the WordPress API as the page loads in an Ionic app using Angular? I am able to retrieve my desired post list, but only when I use a button click event to call the method in the HTML. Since this is my first att ...

Allow Visual Studio Code to create a constructor for Typescript class

When developing Angular 2 apps in Typescript using Visual Studio Code, one common task is writing constructors with their parameter list. Is there a way to save time and effort on this? It would be really helpful if the IDE could automatically generate th ...

Crafting interactive buttons with angular material

I've been working on an angular application where I created 5 mat flat buttons using angular material. <button mat-flat-button [ngClass]="this.selected == 1 ? 'tab_selected' : 'tab_unselected'" (click)="change(1)">B-L1</b ...

Steps to define a JavaScript mixin in VueJS

Currently, I am working on a Vue project with TypeScript and in need of using a mixin from a third-party library written in JavaScript. How can I create a .d.ts file to help TypeScript recognize the functions defined in the mixin? I have attempted the fol ...

Beneath the Surface: Exploring Visual Studio with NPM and Typescript

Can you explain how Visual Studio (2015) interacts with external tools such as NPM and the Typescript compiler (tsc.exe)? I imagine that during the building of a solution or project, MSBuild is prompted to execute these additional tools. I'm curious a ...

Angular : Is toFixed() functioning properly with one value but not with the other one?

My form has 2 inputs, each calling the calculeSalaire() function. calculeSalaire() { this.fraisGestion = this.getFraisGestion(); this.tauxFraisGestion = this.getTauxFraisGestion(); this.salaireBrut = this.getSalaireBrut(); this.salaireNet = this.g ...

Using HTTP POST to subscribe and implementing a try-catch block

When using Angular2's http.post, I encountered an issue where the headers were not sent to the CORS server. To address this, I attempted to create a loop that would retry the request until it succeeds. However, this resulted in an endless loop. How ca ...

What is the significance of the exclamation mark in Vue Property Decorator?

As I continue to explore the integration of TypeScript with Vue, I have encountered a query about the declaration found in the Vue property decorator documentation. @Prop({ default: 'default value' }) readonly propB!: string ...

Issue encountered when transitioning from Angular 8 to Angular 9: Value discrepancy at index 0

While updating my Angular project from version 8 to 9, I encountered an issue after following the update guide on update.angular.io and running npm update. When attempting to compile the website using ng serve, the following error occurred: ERROR in Faile ...

What is the process of declaring a variable within a class in TypeScript?

When setting up an instance variable inside my Angular component like this: @Component({ selector: 'app-root', templateUrl: './app.component.html', //template: `` styleUrls: ['./app.component.css'] }) export class AppCo ...