Can anyone provide guidance on how to define a generic constraint that guarantees the value types of a shared key in two different types are identical?

I am currently working on a function that copies fields from one object to another, and I have two main requirements:

  • The key provided must be common between both objects.
  • The value type corresponding to the given key should be the same in both objects.

How can I enforce the second constraint? Below is my current implementation.

function copyField<T1, T2>(input: T1, output: T2, key: keyof T1 & keyof T2) {
  const keyValue = input[key]
  output[key] = keyValue // How can I ensure that the key value is the same for both?

  // ^ Error: Type 'T1[keyof T1 & keyof T2]' is not assignable to type 'T2[keyof T1 & keyof T2]'.
  // Type 'T1' is not assignable to type 'T2'. 'T2' could be instantiated with an
  // arbitrary type which could be unrelated to 'T1'.
}

type A = { field: string }
type B = { field: string }

copyField<A, B>({field: 'hello'}, {field: 'world'}, 'field')

Live example on typescriptlang.org

Update

An answer below got me closer to what I want but still doesn't guarantee that the value for the given key is exactly the same. This demonstrates how mismatched types can still compile because the constraint only guarantees that the type is present somewhere on both objects, not necessarily for the given key.

function copyField<K extends string, V>(input: {[k in K]: V}, output: {[k in K]: V}, key: K) {
  const keyValue = input[key]
  output[key] = keyValue
}

type A = { s: string, mismatch: string }
type B = { s: string, mismatch: number }
//                                 ^--- these don't have the same type

const a: A = {s: 'hello', mismatch: 'hello'}
const b: B = {s: 'world', mismatch: 123}

// This compiles, but I want it to only accept 's'
copyField(a, b, 'mismatch')

Answer №1

Here is a potential solution:

type MatchingKeys<First, Second> = {
    [Key in keyof First & keyof Second]:
        [First[Key], Second[Key]] extends [Second[Key], First[Key]] ? Key : never
}[keyof First & keyof Second]

This code creates a new type by comparing keys from two different objects and determining if their corresponding values are of the same type.

A key K is retained only if

[First[K], Second[K]] extends [Second[K], First[K]]
, indicating that the types of the values at that key match up. If this logic is too strict for your needs, you can adjust it to First[K] extends Second[K] to focus solely on the compatibility of value types.

Play around with this code on the TypeScript Playground!

Answer №2

If it's relevant, here is the approach I would take:

function copyField<O, K extends keyof O, I extends Record<K, O[K]>>(
    input: I,
    output: O,
    key: K
) {
    const i: Record<K, O[K]> = input;
    output[key] = i[key];
}

The concept behind this function is to allow the type O of the variable output to be flexible, and enable any key K of O to be used as an argument. Moreover, the type I of input is restricted to Record<K, O[K]>, ensuring that input must have a property at key K with a value that can be assigned to O[K].

It is important to note that strict identity between I[K] and

O[K]</code is not required. What matters is that <code>I[K] extends O[K]</code, allowing for successful copies while preventing erroneous ones.</p>
<p>Additionally, widening <code>input
from type I to its constraint Record<K, O[K]> was necessary within the function implementation to satisfy the compiler.


This function delivers the following desired behavior:

copyField({ good: 0, bad: "", whatever: true }, { good: 4, bad: 1 }, "good"); // This is acceptable
copyField({ good: 0, bad: "", whatever: true }, { good: 4, bad: 1 }, "bad"); // This will result in an error
// ------------------> ~~~
// Type 'string' is not assignable to type 'number'

Note that the error arises in the input parameter rather than the value assigned to "bad" for key. Specifically, it indicates that the property bad is a string which cannot be written into a number property.

Similarly, this setup allows copying from a narrower type to a broader type but prevents the reverse:

copyField({ x: 123 }, { x: Math.random() < 0.5 ? 456 : "789" }, "x"); // This works fine
copyField({ x: Math.random() < 0.5 ? 456 : "789" }, { x: 123 }, "x"); // This will throw an error
// ---------------> ~
// Type 'string | number' is not assignable to type 'number'

The first operation succeeds because copying the number value from input into the wider number | string value of output is safe. Conversely, the second operation fails because it's unsafe to copy a number | string value from input to the narrower number value of output. The accompanying warning clarifies the issue.


Link to code on Playground

Answer №3

Give this a shot

function replicateValue<A extends string, B>(source: {[x in A]: B}, destination: {[x in A]: B}, keyToCopy: A) {
  const valueToCopy = source[keyToCopy]
  destination[keyToCopy] = valueToCopy
}

replicateValue({item: 'apple'}, {item: 'banana'}, 'item')

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

Error encountered within eot file using file-loader and webpack

I am facing an issue while trying to integrate React Rainbow Components with Next.js (TypeScript). I encountered a problem with importing fonts, which led me to use webpack along with the url-loader. However, despite my efforts, I keep encountering the er ...

In Ionic3, NgFor is designed to work with Iterables like Arrays for binding

Trying to convert an Ionic storage object into an array for better readability in an ion view. Encountering the error: NgFor only supports binding to Iterables such as Arrays. What is the best way to convert an object to an array? Do I need to use forEac ...

Detecting hoverover on boostrap card-header using TypeScript

How can I make a button in the header of a bootstrap card only visible when the user hovers over it? Is there a way to achieve this using ngIf="card-header.hoverover"? I need to find a method to detect the hover event either in the HTML or on the TypeScr ...

Unable to store the outcomes from [ngbTypeahead] in [resultTemplate]

I'm trying to integrate ngbTypeahead into my HTML using the code snippet below <ng-template #rt let-r="result" let-t="term"> <ngb-highlight [result]="r.FirstName" [term]="t"></ngb-highlight> </ng-template> <input name ...

Tips on exporting a basic TypeScript class in an Angular 4 module

I am facing a challenge with packaging a TypeScript class as part of an Angular module for exporting it as a library using ng-packagr. For instance, here is my class definition - export class Params { language: string ; country: string ; var ...

Angular 4 yields an undefined result

I'm currently working on this piece of code and I need help figuring out how to return the value of rowData in this scenario. private createRowData() { const rowData: any[] = []; this.http .get(`/assets/json/payment.json`) .toPromise() .then(r ...

Code in Javascript to calculate the number of likes using Typescript/Angular

I have encountered a challenge with integrating some JavaScript code into my component.ts file in an Angular project. Below is the code snippet I am working on: ngOninit() { let areaNum = document.getElementsByClassName("some-area").length; // The pr ...

Exploring the automatic type inference functionality of Typescript for generic classes

Although it may seem simple, I am struggling to pinpoint the cause of this error. I have been searching for a solution for quite some time, but I have yet to find one. class MyClass<T > { property: T = 5 // Error Here: Type '5' is not as ...

Angular2: Ways to update components with resolver dependencies?

In my project, I have three separate components, each with its own resolver that retrieves data from distinct APIs. These components all depend on a shared URL provided by a service. My goal is to ensure that when the URL changes, each component refreshes ...

I find myself hindered by TypeScript when trying to specify the accurate constraints for getUserMedia

I'm having difficulty getting a screen to stream within my Angular 5 Electron application. I am utilizing the desktopCapturer feature provided by Electron. Below is an excerpt of my code: loadCurrentScreensource() { desktopCapturer.getSources({ ...

Incorporating SVG graphics within a SharePoint web part

I am in the process of developing a webpart for SharePoint using the SharePoint Framework, TypeScript, and ReactJS. I have encountered an issue while trying to incorporate an svg image into my webpart code, resulting in build errors. Initially, I used the ...

Tips for troubleshooting a Typescript application built with Angular 2

What is the best approach for debugging an Angular 2 Typescript application using Visual Studio Code or other developer tools? ...

Ways to retrieve data beyond the constructor

Here is the code snippet from my component.ts: export class OrganizationsComponent { public organizations; constructor(public access: OrganizationService) { this.access.getOrganizations().subscribe((data => { this.organizations = data; ...

Exploring ways to customize or replace the extended CurrencyPipe type in Angular

I'm currently working on reusing an existing currency pipe from Angular common. My objective is to truncate the .00 when the value is round. Here's the code I've come up with: /** Transform currency string and round it. */ @Pipe({name: &apo ...

How to use RxJs BehaviorSubject in an Angular Interceptor to receive incoming data

Being a newcomer to rxjs, I grasp most operators except for the specific use case involving BehaviorSubject, filter, and take. I am working on renewing an oauth access and refresh token pair within an Angular interceptor. While reviewing various codes fro ...

After updating next.config, the NextJS + NextAuth middleware doesn't seem to be triggered

I'm currently utilizing <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b0ded5c8c49dd1c5c4d8f0849e81889e87">[email protected]</a> & <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemai ...

Error in TypeScript when type-checking a dynamic key within a record

When explicitly checking if a property is not a string using a variable key, Typescript throws an error saying Property 'forEach' does not exist on type 'string'. let params: Record<string, string | string[]>; const key = 'te ...

What method can I use to reach the controller of a route that has been exported to another route?

I am currently incorporating nested routes in my TypeScript project. I have set up a router named review.route.ts with the following code snippet: > review.route.ts import { createReview } from "@controller..."; const reviewsRouter = Router() as Expre ...

Experiencing strange sorting issues with @dnd-kit when moving a draggable element that contains multiple items from the list

Encountering a problem while using dnd-kit! When I switch the draggable with the first element from the list (either above or below), it works smoothly. However, continuing to drag and swap with the subsequent elements leads to unusual sorting behavior. H ...

The pattern validator in Angular 2 Reactive Forms does not seem to be functioning as expected

I need help with my postal code validation. I have defined the following pattern: Validators.pattern("/^[ABCEGHJKLMNPRSTVXY][0-9][ABCEGHJKLMNPRSTVWXYZ][0-9][ABCEGHJKLMNPRSTVWXYZ][0-9]$/")]] Even though 'K1K1A1' should be a valid postal code, th ...