Using the keyof lookup in a Typescript interface is a powerful way to

I'm looking for a solution similar to:


interface Operation<T, K extends keyof T> {
  key: keyof T;
  operation: 'add' | 'remove';
  value: T[K];
}

but without the necessity of passing K as a template. Essentially, I want to be able to achieve:

interface User {
  name: string;
  age: number;
}

// this is acceptable
const operation: Operation<User> = {
  key: 'name',
  operation: 'add',
  value: 'the value',
}
// this is acceptable
const operation: Operation<User> = {
  key: 'age',
  operation: 'add',
  value: 123,
}

// this is not valid
const operation: Operation<User> = {
  key: 'age',
  operation: 'add',
  value: '123',  // type is wrong, should be number
}

Any suggestions on how I can accomplish this?

Answer №1

In my opinion, it is possible to enhance the object creation process by using functions. The concept is outlined as follows:

To begin with, create a type that retrieves the value type of any property:

type ValueType<T, K> = K extends keyof T ? T[K] : never;

Next, define the Operation type:

type Operation<T, K> = {
  key: K
  operation: 'add' | 'delete'
  value: ValueType<T, K>
}

The crucial step here is to define the build function instead of directly creating the object because we want to carefully handle the input arguments:

type BuildOperation<T> = <K>(arg: K) => (arg: Operation<T, K>) => Operation<T, K>

function build<T>(): BuildOperation<T> {
  return (key) => (props) => ({
    key,
    ...props,
  });  
}

Finally, we can utilize our function by specifying the key as as const to ensure Typescript does not infer it as a string:

type User = { name: string; age: number }

const obj = build<User>()('age' as const)({
  key: 'age',
  value: 20, // It will indicate this is a number
  operation: 'add',
});

Answer №2

interface Task<T, K extends keyof T> {
  action: "start" | "complete";
  data: T[K];
}

interface Member {
  username: string;
  points: number;
}

// example of valid task
const task1: Task<Member, "username"> = {
  action: "start",
  data: "task123",
};

// another valid task
const task2: Task<Member, 'points'> = {
  action: "complete",
  data: 50,
};

// invalid task
const task3: Task<Member, 'points'> = {
  action: "start",
  data: "50", // incorrect type, should be a number
};

I suggest using keyof. However, I do see the point raised in the comments. Why bother if it doesn't affect runtime behavior? It's just for compile-time validation.

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

JavaScript can retrieve the default page name that is hidden within the browser's URL

When a URL is entered without a specific file name at the end, such as "www.google.com," the server typically serves a default file like "index.html" or "default.aspx." In this scenario, if the browser displays "www.google.com," I am looking to extract the ...

Enhance your Three.js development with TypeScript autocomplete

In my project using Node.js, Typescript, and Three.js, I have set up the commonjs syntax for module imports in my tsconfig.json file like this: { "compilerOptions": { "module": "commonjs" } } I installed Three.js via NPM and created a typescript ...

Reloading issue with NextJs when utilizing next-i18next for translations on a specific

Having trouble with next-i18next in my app. Implemented everything correctly, but the layout keeps rerendering on pages with getStaticProps. Need to find a way to prevent this. Created a file named withStaticTranslations.ts for pages, but when trying to l ...

Connecting radio buttons to data in Vue using render/createElement

How do you connect the value of a selected radio button to a specific variable in the data object within a Vue application when using the render/createElement function? ...

Unlock the potential of JavaScript by accessing the local variable values in different functions

I've been struggling for days to find a solution to this issue... https://i.stack.imgur.com/KDN7T.jpg https://i.stack.imgur.com/tOfCl.jpg The image above illustrates the challenge I'm facing - trying to apply data values from elsewhere to the ...

The ComponentDidUpdate function treats the previous state (prevState) and the current state (this

Initially, I had a state update function that looked like this: doChangeValue(data) { const dataNew = this.state.data dataNew[data.id] = data.value this.setState({ ...dataNew, [dataNew[data.id]]: data.value}) } However, I realized that thi ...

Showcasing interactive column titles by employing angularjs in an html table

After preparing my data, I aim to showcase it in an HTML table. However, a complication arises each time the $http service is called as it returns a varying number of columns (n). Essentially, I wish to have the first row of the data serve as column names, ...

Angular 6 - The state of the expression was altered after it was verified, different types of constructions

During the build process in debug mode with ng build, I am encountering errors in some components. However, when I switch to production mode using ng build --prod, these errors disappear. I am curious as to why this discrepancy is occurring. Error: Expre ...

What methods can I use to enable web browsers to remember form field data that is not transmitted in the usual manner?

The code provided here is almost perfect in terms of functionality. function post_form() { http=new XMLHttpRequest(); http.onreadystatechange=function() { if (http.readyState==4 && http.status==200) some_div.innerHTML=http.responseText; ...

How to apply a single pipe to filter columns in Angular 2 with an array of values

I need to sort through an array of objects using multiple array string values. Here is an example of how my array of objects looks like: [{ "name": "FULLY MAINTAINED MARUTI SUZUKI SWIFT VDI 2008", "model": "Swift" }, { "name": "maruti suzuki ...

Setting up a CentOs Linux environment to host and run a node.js server

Just installed node.js on my new VPS server. I have written a basic script called "server.js" and it is currently running on port 8080. The location of the script is in the "var/www/html/" directory. However, whenever I try to access my domain, it only dis ...

Error handling in Angular is not properly managing the custom exception being thrown

I am currently working on an Angular 12 application and I have a requirement to implement a custom ErrorHandler for handling errors globally. When I receive an error notification from the backend, I subscribe to it in the ToolService using this.notificati ...

Tips for adjusting the autocomplete maxitem dynamically

I am working on a multi autocomplete feature and I have the following code. Is there a way to dynamically set the maximum number of items based on the value entered in another text field? <script> $(function () { var url2 = "<?php echo SI ...

Troubleshooting "Nodejs Socket hang up & ECONNRESET" when sending an HTTP post request from a Meteor app to a Node.js

Utilizing a node server for managing all push notification services like GCM and APN. I have 2 separate servers in operation - one hosting Meteor and the other running Node.JS to handle push notifications. (Both servers are distinct) The primary applicat ...

No overload error encountered with TypeScript function call

I am working on an async function that communicates with the backend and I need it to handle axios error messages. My goal is to display these messages in a form. export async function register( prevState: string | undefined, formData: FormData ) { t ...

Mastering Protractor's end-to-end control flow and managing time outs

When testing an angular app using protractor, I encountered a strange issue recently. Every now and then, or since a recent update, protractor seems to stall or slow down significantly. After investigating the problem, I discovered that a simple someEleme ...

Unleash the power of drag-and-drop functionality with cdkDrop

I am currently tackling a project that requires the implementation of a drop zone functionality where elements can be dragged from a list and dropped in a zone for free movement. I intend to utilize a cdkDropList for the zone due to its comprehensive list ...

Utilizing JavaScript variable modifiers for enhanced functionality

I'm trying to find a way to perform a case-insensitive search for a variable within a string. To achieve this, I'm using the /gi modifier to ignore case sensitivity. However, the challenge arises because identifiers only accept direct strings rat ...

Tips on accessing close autoComplete/TextField title in AppBar

Looking to add a search bar and login button in the AppBar, where the search Bar is positioned close to the title. The desired order for the AppBar components should be as follows: Title SearchBox LoginButton How can this be achieved? Below is th ...

Merging the outcomes of a JSON call

Presently, I have an async function that returns a JSON response in the form of an array containing two objects. Please refer to the screenshot. How can I merge these objects to obtain: [{resultCount: 100, results: Array(100)}] I attempted the followin ...