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

Exploring TypeScript and React: Redefining Type Definitions for Libraries

As I transition from JSX to TSX, a challenge has arisen: My use of a third-party library (React-Filepond) This library has multiple prop types The provided types for this library were created by an individual not affiliated with the original library (@ty ...

Retrieve data from an HTML form within an Angular 2 ag-grid component

I'm facing a challenge with incorporating form values from a modal into an ag-grid array in my HTML file. I'm unsure of the process to achieve this. Below is an excerpt from my file.html: <template #addTrainContent let-c="close" let-d="dismi ...

Executing Promises in TypeScript Sequentially

I have a collection of doc objects and I need to send an API request for each doc id in the list, executing the requests in a sequential manner. In my Typescript code, I am using Promise.all and Promise.allSelected to achieve this. [ { "doc_id&q ...

Mapping intricate entities to intricate DTOs using NestJS and TypeORM

Currently, I am using the class-transformer's plainToClass(entity, DTO) function to transform entities into DTO objects. In addition, I have implemented the transform.interceptor pattern as outlined in this source. I make use of @Expose() on propert ...

Using React and TypeScript to pass member variables

My component Child has a member variable that can change dynamically. While I am aware of passing props and states, is there a more elegant solution than passing member variables through props or other parameters? class Child extends React.Component< ...

There is no overload match for the HttpClient.get call at this time

I'm trying to set up a file download feature using a blob, but I need to extract the filename from the server's "content-disposition" header. Here's the code I have: const header = {Authorization: 'Bearer ' + token}; const config ...

What is the best way to search for and isolate an array object within an array of objects using Javascript?

I am attempting to narrow down the list based on offerings const questions = [ { "id": 2616, "offerings": [{"code": "AA"},{"code": "AB"}]}, { "id": 1505, "offerings": [ ...

Tips for Invoking an Overloaded Function within a Generic Environment

Imagine having two interfaces that share some fields and another interface that serves as a superclass: interface IFirst { common: "A" | "B"; private_0: string; } interface ISecond { common: "C" | "D"; private_1: string; } interface ICommo ...

Having trouble resolving errors in Visual Studio Code after failing to properly close a parent function? Learn how to fix this issue

Here we have the code starting with the construct function followed by the parents function public construct() //child { super.construct; } protected construct() //parent { } The issue arises where I am not receiving an er ...

The module cannot be located due to an error: Package path ./dist/style.css is not being exported from the package

I am facing an issue with importing CSS from a public library built with Vite. When I try to import the CSS using: import 'rd-component/dist/style.css'; I encounter an error during the project build process: ERROR in ./src/page/photo/gen/GenPhot ...

Common problems encountered post Typescript compilation

I encountered the same problem. Below is my tsconfig settings: "compilerOptions": { "module": "commonjs", "moduleResolution": "node", "newLine": "LF", &q ...

What is the reason that specifying the type of function parameters does not result in conversion being

Seeking clarity here. I have a TypeScript function (using version 1.7) that is linked to the change event of a select dropdown in HTML: toggleWorker(stsID: number): void { // get status object by selected status ID let cStatus: Status = this.s ...

Tips on using constructor functions and the new keyword in Typescript

This is a demonstration taken from the MDN documentation showcasing the usage of the new keyword function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } const car1 = new Car('Eagle', 'Talon TSi&apos ...

Using Node.js: Only bring in the necessary function, don't execute the entire file

Today, I stumbled upon an interesting observation and I'm curious about the peculiar behavior of node in this scenario. I have two files structured as follows: src/api/index-api.ts src/worker/index-worker.ts Both of these files contain a simple con ...

When Typescript calls the toString method on a Function, it produces unexpected characters like "path_1, (0, promises.writeFile)"

I'm currently attempting to convert a function into a string for transmission to a worker thread for execution. However, when imported code is included, the resulting string contains strange characters. import { HttpStatus } from '@nestjs/common& ...

What makes fastify-plugin better than simply calling a regular function?

I recently came across a detailed explanation of how fastify-plugin operates and its functionality. Despite understanding the concept, I am left with a lingering question; what sets it apart from a standard function call without using the .register() metho ...

What is the best way to simulate a function within an object using Jest and Typescript?

I am currently working on a function that calls the url module. index.svelte import {url} from '@roxi/routify'; someFunction(() => { let x = $url('/books') // this line needs to be mocked console.log('x: ' + x); }); ...

A guide on exporting table data to PDF and enabling printing features in Angular 7

Can anyone provide guidance on how to export my dynamic table data into Excel, PDF, and for printing using the appropriate Angular Material components and npm plugins? I have successfully exported the data as an Excel file, but am struggling with exporti ...

Mastering the art of leveraging generics in conjunction with ngrx actions

Currently, I am in the process of developing an Angular 7 application that utilizes the NGRX store. In our application, we have numerous entities, each with its own dedicated list view. I decided to explore using generics with the NGRX store to avoid writi ...

Can TypeScript provide a method for verifying infinite levels of nested arrays within a type?

Check out this example The concept behind this is having a type that can either be a single object or an array of objects. type SingleOrArray<T> = T | T[]; The structure in question looks like this: const area: ItemArea = [ { name: 'test1& ...