Is there a way to limit a TypeScript function to only accept `keyof` values corresponding to fields of a specific type?

I'm currently working on developing a function that updates a field in an object if the passed-in value differs from the existing one. The code snippet I have so far is

type KnownCountryKeys = "defaultCountry" | "country";
type IHasCountry = {
  [key in KnownCountryKeys]: Country;
};

export async function maybeUpdateCountry<THasCountry extends IHasCountry>(
  dto: THasCountry,
  countryKey: KnownCountryKeys,
  newCountryCode: string
) {
  // Pseudo-Code

  // I want TypeScript to verify that `dto[countryKey]` matches the Country type.
  if (dto[countryKey].code != newCountryCode) {
    const newCountry = //...validateCountry
    dto[countryCode] = newCountry;
  }
}

One drawback of this method is that dto is limited to the fields specified in KnownCountryKeys, resulting in error ts2345. Attempting to make the type more flexible by using an interface leads to error ts2464. Moreover, I aim to eliminate the need for KnownCountryKeys as it raises concerns.

The goal is for this function to handle objects of any shape, validate a string key as a Country type field, and determine whether the country requires updating in a completely type-safe manner.

Answer №1

Upon understanding your query correctly, it seems you are searching for something along the lines of:

type IHasCountry<K extends KnownCountryKeys> = { [key in K]: Country; };

export function maybeUpdateCountry<K extends KnownCountryKeys>(
    dto: IHasCountry<K>, countryKey: K, newCountryCode: string) {
// ...

UPDATE:

If we assume that the country code is an arbitrary string but the DTO must contain a field named 'country code' with the type of Country, then consider the following:

class Country { constructor(readonly name: string) { } };

function maybeUpdateCountry<K extends string, T extends { [key in K]: Country }>(
  dto: T, countryKey: K, newCountryCode: string): void {}


const dto1 = { 'pl_PL': new Country('Poland') };
const dto2 = { 'fr_CH': new Country('Switzerland') };
const dto3 = { 'fr_FR': new Country('France') };


maybeUpdateCountry(dto1, 'pl_PL', 'Pologne'); // Accepted
maybeUpdateCountry(dto2, 'fr_CH', 'Suisse'); // Accepted

maybeUpdateCountry(dto2, 'fr_FR', 'Suisse'); 
// ERROR: 
// Argument of type '{ fr_CH: Country; }' is not assignable to parameter of type '{ fr_FR: Country; }'.
//  Property 'fr_FR' is missing in type '{ fr_CH: Country; }' but required in type '{ fr_FR: Country; }'.


maybeUpdateCountry(dto2, 'test', 'Test'); 
// ERROR: 
// Argument of type '{ fr_CH: Country; }' is not assignable to parameter of type '{ test: Country; }'.
//  Property 'test' is missing in type '{ fr_CH: Country; }' but required in type '{ test: Country; }'.

maybeUpdateCountry({ hello: 'bonjour' }, 'test', 'Test');
// ERROR:
// Argument of type '{ hello: string; }' is not assignable to parameter of type '{ test: Country; }'.
//  Object literal may only specify known properties, and 'hello' does not exist in type '{ test: Country; }'.

maybeUpdateCountry({ test: 'Not a country' }, 'test', 'Test');
// ERROR:
// Type 'string' is not assignable to type 'Country'

maybeUpdateCountry({ test: new Country('Testland') }, 'test', 'Test'); // Accepted

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

Can you explain the concept of widening in relation to function return types in TypeScript?

Recently, I've observed an interesting behavior in TypeScript. interface Foo { x: () => { x: 'hello' }; } const a: Foo = { x: () => { return { x: 'hello', excess: 3, // no error } }, } I came acro ...

Exploring the Applications of Directives in Multiple Modules within Angular 2

When I declare the directive in two modules, I get an error that says Type PermissionDirective is part of the declarations of 2 modules. However, when I declare it in only one module, I receive an error stating Can't bind to 'isPermission' s ...

Node corrupting images during upload

I've been facing an issue with corrupted images when uploading them via Next.js API routes using Formidable. When submitting a form from my React component, I'm utilizing the following functions: const fileUpload = async (file: File) => ...

How can one correctly cast or convert an array of objects to the interface that extends the objects' parent interface in Typescript?

Question: How can I optimize the usage of method sendItemIdsOverBroadcastChannel to reduce message size? interface IItemId { id: number; classId: number; } interface IItem extends IItemId { longString: string; anotherLongString: string } inte ...

Retrieve the mfData value from the TypeScript file in order to perform operations on it within the Angular 2 framework

I have a snippet of code that iterates through data from stacklist_table, which is a JSON array, and displays it in a table format. The stacklist_table contains a full list of objects, but I only need a subset of these objects so I have applied some filter ...

My instance transforms with the arrival of a JSON file

I'm grappling with a query about data types in TypeScript and Angular 2. I defined a class in TypeScript export class product{ public id:number; public name:string; public status:boolean; constructor(){} } and I initialize an instanc ...

Working with e.charcode in TypeScript allows for easy access to

I'm having trouble understanding why this code snippet is not functioning as expected. const addRate = (e: { charCode: KeyboardEvent }) => { if (e.charCode >= 48) { ... } } The error message I receive states: 'Operator '>=& ...

What is the best way to prevent TSC from including the node_modules folder in my compilation

I have encountered a persistent issue that all previous solutions have failed to address. Here is the snippet from my tsconfig file that I believe should resolve the problem: ... "compilerOptions": { "skipLibCheck": true, ... &quo ...

What steps can be taken for TypeScript to identify unsafe destructuring situations?

When working with TypeScript, it's important to prevent unsafe destructuring code that can lead to runtime errors. In the example below, trying to destructure undefined can cause a destructuring error. How can we ensure TypeScript does not compile suc ...

Exploring the Implementation of Validation Pipe in class-validator

I am currently exploring how to effectively utilize my validation pipe in combination with class-validator on an API call. My DTO is decorated with class-validator decorators and is performing as anticipated. However, I am interested in utilizing the &apo ...

Function that wraps JSX elements with the ability to infer types through generics

At the moment, this function is functioning properly function wrapElement(elem: JSX.Element) { return ({ ...props }) => React.cloneElement(elem, { ...props }) } I've been using it in this way to benefit from intelliSense for tailwind classes con ...

The 'autoComplete' property cannot be found within the 'IntrinsicAttributes & InputProps' type

I'm currently utilizing Typescript and React, but encountering the following error: Property 'autoComplete' is not found on type 'IntrinsicAttributes & InputProps'. This is how I am using the component: <Input ...

the "then" function is causing issues in my TypeScript Node code

My code looks like this: var fs = require('fs'); var util = require('util'); var files = fs.readdirSync('*/path to my folder that contains subfolders*/') async function getfilenum(){ var files_v_num = [] for(const i in fi ...

Having trouble running tests on Angular service that has a Console constructor parameter

Attempting to incorporate a logging service in my project, the name service implies its purpose. During initialization, a specific implementation of the logging service is selected. This implementation utilizes the console for logging messages. Ensuring t ...

Why does React redirect me to the main page after refreshing the page, even though the user is authenticated in a private route?

In the process of developing a private route component that restricts unauthenticated users and redirects them to the homepage, we encountered an issue upon page refresh. The problem arises when the current user variable momentarily becomes null after a ...

Comparison of env.local and String variables

I encountered an access denied error with Firebase. The issue seems to arise when I try passing the value of the "project_ID" using an environment variable in the backend's "configs.ts" file, as shown in the code snippet below: import 'firebase/f ...

Understanding the significance of an exclamation point preceding a period

Recently, I came across this code snippet: fixture.componentInstance.dataSource!.data = []; I am intrigued by the syntax dataSource!.data and would like to understand its significance. While familiar with using a question mark (?) before a dot (.) as in ...

Using Higher Order Components in React with TypeScript to pass a component as a prop

Exploring the steps outlined in this guide: https://reacttraining.com/react-router/web/example/auth-workflow. Attempting to replicate the code: const PrivateRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={props = ...

Clicking the button on an Ionic/Angular ion-item will toggle the visibility of that item, allowing

I'm currently working with Ionic 5 / Angular and I have a collection of ion-item's, each containing a button. Take a look at the code snippet below: <ion-list> <ion-item> <ion-label>One</ion-label> ...

The form will not appear if there is no data bound to it

Can anyone help me with displaying the form even when the data is empty in my template? <form class="nobottommargin" *ngIf="details" [formGroup]="form" (ngSubmit)="onSubmit(form.value)" name="template-contactform"> <div class="col-sm-12 nopad ...