Is there a way in TypeScript to specify that the parameters of an interface should be the keys of an object?

Here is the code I'm working with:

interface IOrder {
  id: string
  created: string
  name: string
  age: number
}

type OrderKey = keyof IOrder

const columnNames: OrderKey[] = ['id', 'name', 'age', 'created']
const colValGenerators: {[key: string]: (obj: any) => any} = {
  age: (obj: any) => Math.round((Date.now() - Date.parse(obj.created)) / 1000 / 60 / 60 / 24)
}

const orders: IOrder[] = [
  {id: '1', created: 'Thu, 03 Feb 2022 14:59:16 GMT', name: 'John Doe', age: 0},
  {id: '2', created: 'Thu, 04 Feb 2022 14:59:16 GMT', name: 'Jane Doe', age: 0}
]

for (const order of orders) {
  for (const colName in colValGenerators) {
    order[colName] = colValGenerators[colName](order)  //ERROR
  }
}

At the marked line //ERROR, I encounter a TypeScript error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ id: string; created: string; name: string; age: number; }'.
  No index signature with a parameter of type 'string' was found on type '{ id: string; created: string; name: string; age: number; }'.ts(7053)

The issue seems to stem from the fact that the keys in colValGenerators can be arbitrary strings and not necessarily matching those in the IOrder interface.

In attempting to address this, I modified:

const colValGenerators: {[key: string]: (obj: any) => any}

to:

const colValGenerators: {[key: OrderKey]: (obj: any) => any}

(Substituting string with OrderKey.) However, this adjustment led to another error:

An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.ts(1337)

Considering this new challenge, I delved into mapped object types. While exploring the option of using Property in keyof IOrder, I encountered an error mentioning No overload matches this call..

If anyone could provide guidance on how to resolve this issue, I would greatly appreciate it. Since my knowledge of TypeScript is limited, alternative solutions apart from my current approach are also welcome.

Answer №1

With TypeScript version 4.6 and above, you can streamline your code to enhance type safety verification without introducing unnecessary redundancy. The relationship between the order and colName values within a loop has become more coherent thanks to recent advancements in TypeScript. Previously, developers had to contend with either numerous type assertions or manually unwinding loops to accommodate all possible values of colName.

Fortunately, the latest improvements in indexed access inference provide better support by allowing annotations that enable the compiler to recognize such correlations:


To start, it's advisable to assign a more specific type to colValGenerators rather than sticking to a generic format like

{[key: string]: (obj: any) => any}
. By using a mapped type approach, where each property key in OrderKey becomes an optional property in colValGenerators, you can align the callback functions with the appropriate property types from that key. This explicit annotation involving the generic type
K</code helps the compiler maintain the correlation between the index and the output.</p>
<pre><code>// const colValGenerators: {
//    id?: ((obj: IOrder) => string) | undefined;
//    created?: ((obj: IOrder) => string) | undefined;
//    name?: ((obj: IOrder) => string) | undefined;
//    age?: ((obj: IOrder) => number) | undefined;
// }

When iterating over the properties of colValGenerators, it's essential to utilize a type assertion to indicate that only keys present are those specified in OrderKey. For instance:

Object.keys(colValGenerators) as OrderKey[]

Since TypeScript object types are not sealed, runtime checks are necessary to ensure no unexpected keys are produced during iteration. A similar check is also crucial when accessing properties of colValGenerators, even if they are marked as optional. Using columnNames for subsequent operations could simplify this oversight, given its prior annotation as OrderKey[].


Finally, while looping through column names, employing the forEach() method with a generic callback utilizing K extends OrderKey as a type parameter for colName enables the compiler to verify the safety and accuracy of assignments like

order[colName] = colValGenerators[colName](order)
:

for (const order of orders) {
  columnNames.forEach(
    <K extends OrderKey>(colName: K) => {
      const generator = colValGenerators[colName];
      if (generator) { order[colName] = generator(order); } // okay
    }
  );
}

This methodology ensures a desired outcome while adhering to type safety requirements. Leveraging the forEach() array method simplifies the process compared to traditional iterative approaches, making development more efficient and error-resistant.

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

The not-null constraint is violated in the "id" column because of a null value when using Sequelize Typescript

My Database Setup Journey Recently, I embarked on a database adventure where I decided to use Sequelize-Typescript to assist me with all the heavy lifting in terms of database operations. The first step was creating a table called uma_tbl_users, and here ...

Retrieve the radio button value without using a key when submitting a form in JSON

Looking to extract the value upon form submission in Angular, here is the code: In my Typescript: constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController, public formBuilder: FormBuilder, public alertCtrl ...

Tips for passing parameters from an anchor click event in TypeScript

Is it possible to send parameters to a click event from an anchor element, or should we not pass params at all? Here is the function I am using: const slideShow = (e: React.MouseEvent<HTMLAnchorElement> | undefined): void => { console.lo ...

The ngIf statement in the template isn't functioning properly after a refresh; instead, it is causing a redirection to the homepage

I've been developing with Angular 7, trying to display a <div> ... </div> based on multiple values that I declared as : Boolean = false; in the .ts file. These values are updated in ngOnInit, but for some reason, the page keeps redirecting ...

Is it possible to set the state of my datepicker to a previous value only if the new one is determined to be invalid?

I am currently facing an issue with a library I'm utilizing, which has the potential to generate incorrect values that may cause my page to crash. To prevent this, I want to ensure that only valid values (the result of useDateRangePickerState) are app ...

Accessing data from an API and showcasing information on a chart using Angular

I'm currently developing a dashboard application that requires me to showcase statistics and data extracted from my MongoDB in various types of charts and maps using Angular and Spring Boot. The issue I'm facing is that when attempting to consume ...

Nock does not capture the requests - Error: Failed to resolve address ENOTFOUND

Let me provide an overview of the structure in place. Jest is utilized for executing the testing process. Within my jest.config.json file, I incorporate the following line: "globalSetup": "<rootDir>/__tests__/setup.js", Inside setup.js, you will ...

Is there a way to access the badge hidden behind the collapsible menu in bootstrap 4?

After moving from bootstrap 3 to bootstrap 4, my items no longer align properly. I've scoured the entire Internet for a solution, but I've run out of options (and patience.. haha) This is how it currently looks: https://i.sstatic.net/ra22j.png ...

Incorporating an offset with the I18nPluralPipe

Having trouble with my multiselect dropdown and the text pluralization. I attempted to use the I18nPluralPipe, but can't seem to set an offset of 1. ListItem = [Lion, Tiger, Cat, Fox] Select 1 Item(Tiger) = "Tiger", Select 3 Item(Tiger, Cat, Fox) = ...

Sending information from a component inside a router-outlet to a higher-level "Parent" component in Angular 2

Is there a way to transfer data from a component embedded within a router-outlet tag in a "parent" component's HTML template back to the parent component? ...

Make sure to refresh the state of the store whenever there is a change detected in the input

I am experiencing an input delay problem when trying to update the state of a zustand variable in the onChange event. const BuildOrder = (props: { setOpen: Function }) => { const { almacenes, isLoadingAlmacenes } = useGetAlmacenes(); const { article ...

What is the best way to switch a boolean state in React using TypeScript?

Hey there! I'm diving into the world of React and TypeScript. My goal is to toggle a boolean state (true/false) using a handler function. While I've come across solutions in ES6, I'm struggling to grasp how it can be implemented in TypeScri ...

Retrieving a specific user attribute from the database using Angular

Currently, I am working on developing an application that utilizes ASP.NET for the Back End (API) and Angular for the Front End of the application. Within the API, I have set up controllers to retrieve either a list of users from the database or a single ...

Retrieve the text that has been chosen and have it displayed with lines

I'm attempting to extract the selected text and format it with line breaks for a VSCODE Extension. const document = editor.document; const selection = editor.selection; const position = editor.selection.end; const word = document.getTe ...

Ion-List seamlessly integrates with both ion-tabs and ion-nav components, creating a cohesive and dynamic user interface

On my homepage, there is an ion-list. Sometimes (not every time), when I select an item in this list or navigate to the register page using "this.app.getRootNav().push("ClienteCadastroPage")", and then select an input in the registerPage or descriptionPage ...

Angular 2: Enhancing Tables

I am looking to create a custom table using Angular 2. Here is the desired layout of the table: https://i.sstatic.net/6Mrtf.png I have a Component that provides me with data export class ResultsComponent implements OnInit { public items: any; ngO ...

Exploring FileReader in conjunction with React and Typescript

I am facing an issue while trying to upload a JSON file using an input element of type file. When I attempt to use the onload method on FileReader in TypeScript, I receive an error message saying "Cannot invoke an object which is possibly 'null'. ...

Dynamically generating an Angular component and populating it with data

I am currently working with Angular 7/8 and I have some code that adds a new component dynamically. In the parent component, my .ts file includes the following: PARENT COMPONENT Within the .ts file: @ViewChild(InjectDirective) injectComp: InjectDirect ...

Specialized pipe encountering issues with conditional statements that function properly within a function

When using a custom pipe that returns a string based on certain conditions, it seems to always return the first result even when the conditions are not met. However, when the same if/else statements are used in a method, it works fine. transform(orderTy ...

In the production mode, Webpack doesn't compile any code

I recently followed the typescript guide at https://webpack.js.org/guides/typescript/ After running webpack in "production" mode, I noticed that it emitted very minimal output. Below is the code from my src/index.ts file: export function foo() { return ...