Using Typescript to extract and process keys of a certain type from an object

I am dealing with an object type that has keys corresponding to values of various types, including number, string, optional number, and optional string:

interface MyObject {
  mandatoryNumber: number;
  optionalNumber?: number;
  mandatoryString: string;
  optionalString?: string;
}

My goal is to create a function that takes an instance of this object along with a key, where the key should only be of type number or optional number. The function should return twice the value if it is defined, otherwise return 0.

This is what I have attempted so far:

type NumberKeys<T> = {
  [K in keyof T]: T[K] extends number ? K : never
}[keyof T];

function process<K extends NumberKeys<MyObject>>(obj: MyObject, key: K): number {
    const value = obj[key]; // -> Typescript error: Type 'K' cannot be used to index type 'MyObject'
    return value !== undefined ? value * 2 : 0;
}

However, I keep encountering a Typescript error stating: Type 'K' cannot be used to index type 'MyObject'.

Answer №1

Your definition of NumberKeys<T> faces a major issue due to the use of a homomorphic mapped type. This mapped type retains information about whether a property is optional, leading to the inclusion of undefined when dealing with optional properties in your code:

type Ex = NumberKeys<{ a: number, b?: string }>;
// type Ex = "a" | undefined

This presence of undefined causes issues as the compiler complains that indexing with K can't be done on MyObject due to the potential for it to be undefined. To resolve this, you need to eliminate undefined. One approach is to utilize the mapping modifier -? to make all properties required in the result:

type NumberKeys<T> = {
  [K in keyof T]-?: T[K] extends number ? K : never
}[keyof T];

type Ex = NumberKeys<{ a: number, b?: string }>;
// type Ex = "a"

With this adjustment, your error is resolved:

function process<K extends NumberKeys<MyObject>>(obj: MyObject, key: K): number {
  const value = obj[key]; // okay
  return value !== undefined ? value * 2 : 0;
}

However, you still aren't achieving the desired behavior:

type NKMO = NumberKeys<MyObject>;
// type NKMO = "mandatoryNumber"

The removal of undefined doesn't address the absence of optionalNumber. This stems from the fact that -? only eliminates undefined from the result of the mapping and not the original property. In TypeScript, this behavior is labeled as a bug which might never get fixed. To rectify this, consider checking for number | undefined instead of just number:

type NumberKeys<T> = {
  [K in keyof T]-?: T[K] extends number | undefined ? K : never
}[keyof T];

type NKMO = NumberKeys<MyObject>;
// type NKMO = "mandatoryNumber" | "optionalNumber"

This solution works as expected.


An alternative approach is to work with Required<MyObject> instead of MyObject, utilizing the Required utility type (which internally uses -?):

type NKMO = NumberKeys<Required<MyObject>>;
// type NKMO = "mandatoryNumber" | "optionalNumber"

function process<K extends NumberKeys<Required<MyObject>>>(
  obj: MyObject, key: K): number {
  const value = obj[key]; // okay
  return value !== undefined ? value * 2 : 0;
}

Both approaches deal with the issue of optional properties introducing undefined, just in slightly different manners.

Playground link to code

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

Angular 6 offers a dynamic auto complete feature that enhances user

My Angular app has auto-complete functionality enabled. I am using ngFor to iterate over an array of objects and passing the index of the object array to a function for some operations. Below is the code snippet I have tried: template.html <mat-form ...

Implementing undefined value acceptance in yup object when using Material-UI

Even though I have clearly specified that the key is optional in my Form, for some reason my input does not accept undefined as a value. Instead, I keep getting this error message: bonusPercentage must be a number type, but the final value was: NaN (cast ...

Transferring various sets of information into a single array

In an attempt to generate JSON data in an array for passing to another component, I followed a method outlined below. However, being relatively new to this field, my execution may not have been optimal as expected. employeeMoney: any[] = []; employeeId: an ...

Activate the event when the radio button is changed

Is there a way to change the selected radio button in a radio group that is generated using a for loop? I am attempting to utilize the changeRadio function to select and trigger the change of the radio button based on a specific value. <mat-radio-group ...

Sinon - observing a spy that remains inactive, yet the test proceeds to enter the function

Having some trouble with a test function that uses two stubs. The stubs seem to be working fine, as I can see the spy objects inside when I console.log res.json or next. However, the spy is not being called when I make the assertion. The error message read ...

Methods to acquire the 'this' type in TypeScript

class A { method : this = () => this; } My goal is for this to represent the current class when used as a return type, specifically a subclass of A. Therefore, the method should only return values of the same type as the class (not limited to just ...

Creating a TypeScript mixin with a partial class is a useful technique that allows you to

I am attempting to have class A inherit properties from class B without using the extends keyword. To achieve this, I am utilizing a mixin in the following manner: class A{ someProp: String someMethod(){} } class B implements classA{ someProp: String ...

Tips for converting Javascript require to Typescript import using the const keyword

Recently, I've been attempting to import faktory_worker_node from github.com/jbielick/faktory_worker. The README provides the following instructions: const faktory = require('faktory-worker'); faktory.register('ResizeImage', asyn ...

Ensure that the query value remains constant in Express.js

Issue: The query value is changing unexpectedly. // url: "http://localhost:4000/sr?q=%C3%BCt%C3%BC" export const search = async (req: Request, res: Response) => { try { const query = String(req.query.q) console.log("query: &quo ...

Tips for dynamically modifying style mode in Vue.js

I am looking to implement a feature where the style can be changed upon clicking a button. In my scenario, I am using Sass/SCSS for styling. For example, I have three different styles: default.scss, dark.scss, and system.scss. The code for dark.scss look ...

Angular allows for creating a single build that caters to the unique global style needs of every

Currently, I am working on a project for two different clients, each requiring a unique style.css (Global CSS). My goal is to create a single production build that can be served to both clients, who have different domains. I would like the global style t ...

Is there a way to bring in data from a .d.ts file into a .js file that shares its name?

I am in the process of writing JavaScript code and I want to ensure type safety using TypeScript with JSDoc. Since it's more convenient to define types in TypeScript, my intention was to place the type definitions in a .d.ts file alongside my .js fil ...

What is the best way to create a function that requires an argument in TypeScript?

I'm looking to bring in a module that requires an argument in Typescript. This is how it looks in javascript: const cors = require('cors')({origin: true}); // JS What would be the equivalent syntax in Typescript? ...

How come I'm able to access the form's control within setTimeout but not outside of it?

Having just started working with Angular, I came across a strange issue involving forms and setTimeout. When trying to access the form control of an input element inside setTimeout within the OnInit lifecycle hook, it works fine. However, when attempting t ...

What happens when i18next's fallbackLng takes precedence over changeLanguage?

I am currently developing a Node.js app with support for multi-language functionality based on the URL query string. I have implemented the i18next module in my project. Below is a snippet from my main index.ts file: ... import i18next from 'i18next& ...

The type does not meet the requirements set by the class it is inheriting from

Currently, I am in the process of working on a WebSocket Secure (WSS) server utilizing the Node ws library which can be found here. More specifically, I am following the approach outlined in this particular question, although its relevance is yet to be det ...

Guidelines for creating a routing for a child component using Angular

Seeking assistance with setting up routing in an Angular application. I have a main component called public.component, and the auth.component component is inserted from the child module Auth.module using the selector. How can I configure the routing for th ...

Issue: When attempting to read the length of a property in Angular 6, a TypeError is being thrown because the property is

Unable to retrieve values from an array using the TS code below: this.dataservice.get("common/public/getAllCategories", null).subscribe(data => { //console.log('categories'+JSON.stringify( data)); this.categoriesarray = data; }); var ...

Is it possible to assign a property value to an object based on the type of another property?

In this illustrative example: enum Methods { X = 'X', Y = 'Y' } type MethodProperties = { [Methods.X]: { x: string } [Methods.Y]: { y: string } } type Approach = { [method in keyof Method ...

changing an array into json format using TypeScript

Looking to convert an array into JSON using TypeScript. How can I achieve the desired result shown below? let array = ['element1', 'element2', 'element3'] result = [{"value": "element1"}, {"value": "element2"}, {"value": "el ...