Ensuring secure field assignment

Is there a way to correctly type the assignment of a number to an object's field based on certain conditions? I have a function that checks whether a number should be assigned or not, but want to ensure type safety:

function maybeANumber(): number | undefined {
  const n = Math.random();
  return n > 0.5 ? n : undefined;
}

function maybeSetNumber(target: any, field: any) {
  const num = maybeANumber();
  if (num !== undefined) {
    target[field] = num;
  }
}

I used any in the code above to make it work, but how can I improve the typing to catch any errors at compile time?


interface Foo {
  a: string,
  b: number,
}

const foo: Foo = { a: "", b: 0 };

maybeSetNumber(foo, "a"); // This should trigger a compile-time error.
maybeSetNumber(foo, "b"); // This should be accepted without issues.

Any suggestions on how to achieve this? The field names are static, so working with arbitrary strings is not necessary. I've experimented with keyof but haven't been able to fully grasp it.

Answer №1

Utilize generics within your function signature for maybeSetNumber() to specify that the field is a generic property key type (K extends PropertyKey), and the target is of a type containing a number value at that specific key (Record<K, number> using the Record utility type):

function maybeSetNumber<K extends PropertyKey>(target: Record<K, number>, field: K) {
  const num = maybeANumber();
  if (num !== undefined) {
    target[field] = num;
  }
}

This approach will provide you with the desired functionalities:

maybeSetNumber(foo, "a"); // error!
// ----------> ~~~
// Types of property 'a' are incompatible.
maybeSetNumber(foo, "b"); // okay

Please note: TypeScript's soundness isn't perfect, so it may allow unsafe operations when dealing with types narrower than number:

interface Oops { x: 2 | 3 }
const o: Oops = { x: 2 };
maybeSetNumber(o, "x"); // no error, but could be bad if we set o.x to some number < 1

It is also possible to adjust the signature in a way that the error occurs on "a" instead of foo. However, this method is more complex and may require one type assertion as the compiler might not fully grasp the implications:

type KeysMatching<T, V> = { [K in keyof T]: V extends T[K] ? K : never }[keyof T]
function maybeSetNumber2<T>(target: T, field: KeysMatching<T, number>) {
  const num = maybeANumber();
  if (num !== undefined) {
    target[field] = num as any; // need a type assertion here
  }
}

maybeSetNumber2(foo, "a"); // error!
// ----------------> ~~~
// Argument of type '"a"' is not assignable to parameter of type '"b"'.
maybeSetNumber2(foo, "b"); // okay

With this alternative, issues like the one seen with Oops can be avoided,

maybeSetNumber2(o, "x"); // error!

However, there may still be potential edge cases related to soundness. TypeScript often assumes that if a value of type X can be read from a property, then a value of type X can also be written to that property. Although this generally holds true, there are exceptions. In any case, both of these methods are preferable to using any.

Playground link to code

Answer №2

If I were to write the function maybeSetNumber, it would look something like this:

function maybeSetNumber<F extends string>(target: { [key in F]: number }, field: F) {
  const num = maybeANumber();
  if (num !== undefined) {
    target[field] = num;
  }
}

Check out this version on TypeScript Playground

Answer №3

While the solutions proposed by others are practical, I would like to delve into the core reason why achieving this without type assertions is not feasible. To execute the operation target[field]: O[K] = num, we must confirm that O[K]: number. However, there are limitations:

  • Generics in TypeScript can only have static constraints defined within the angle brackets of a function's declaration;
  • These constraints are limited to a single generic type variable and a subtyping relation;
  • The language design enforces constraints to flow forward from the angle brackets to the body, but it does not allow for constraints to propagate backward: constraining generics based on arguments or body content is not supported.

Unfortunately, our desired assertion about O[K] involves more than one type variable, which cannot be accommodated by the current structure of TypeScript's type contexts.

This unidirectional constraint propagation mechanism is reminiscent of other languages like Haskell with type contexts. Whether this limitation stems from theoretical or implementation challenges in the type checker realm remains unclear. In any case, the conclusion stands that TypeScript's type contexts lack the sophistication necessary to facilitate such assertions at present.

Answer №4

When dealing with programming languages that lack union types, a common approach is to utilize multiple return values for the desired functionality. This method is particularly effective in languages that support multiple returns, as it allows for the retrieval of two distinct values - such as a numerical value and an indicator specifying whether or not to use said number. Essentially, this workaround involves repurposing the return value's type as a flag attribute associated with the returned number. If the "use" flag is set to false, then a dummy integer can be returned instead.

To see how this principle can be implemented in Javascript, you can refer to this example: Return multiple values in JavaScript?

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

Tips on effectively rendering child components conditionally in React

My components currently consist of an AddBookPanel containing the AddBookForm. I am looking to implement a feature where the form is displayed upon clicking the 'AddBookButton', and hidden when the 'x' button (image within AddBookForm c ...

What is the recommended way to handle data upon retrieval from a Trino database?

My goal is to retrieve data from a Trino database. Upon sending my initial query to the database, I receive a NextURI. Subsequently, in a while loop, I check the NextURI to obtain portions of the data until the Trino connection completes sending the entire ...

Is there a way to transfer data from a custom hook to a component seamlessly?

I am facing an issue with a custom hook that passes parameter data along with fetched data to the Settings component. Inside Settings, I have a hook called setData11 in useEffect and I am trying to set the data passed from useTable but encountering an er ...

Using TypeScript along with Nuxt.js and Vuex to access methods from an imported class

Currently, I am in the process of developing a nuxt.js application with typescript and my goal is to segregate the API Calls from the vuex store. However, I've encountered an issue where it seems like I cannot utilize the methods when importing the cl ...

Learn how to implement AuthContext and createDataContext in React Native Expo development using TypeScript

AuthContext.tsx import createDataContext from './createDataContext'; import serverApi from '../api/server'; const authReducer = ({state, action}: any) => { switch(action.type){ default: return state; } } ...

What is the best way to create a dynamic URL linking to an external site in Angular 5?

When attempting to create a link like this: <a [href]="getUrl()">click me</a> getUrl() { return this.domSanitizer.bypassSecurityTrustUrl('http://sampleUrl.com'); } The link is not clickable. When hovering over the ...

"Enhance your web development with TypeScript and react-select

I find myself in a peculiar predicament. Currently, I am immersing myself in learning TypeScript and here is a brief overview of what transpired so far: const [selectedBankCode , setSelectedBankCode] = useState<string | undefined>(); const [selecte ...

Troubleshooting fastify library errors related to ajv validation

Every time I try to build my TypeScript code, I encounter these errors: The following errors are showing up in my TypeScript code: 1. node_modules/@fastify/ajv-compiler/types/index.d.ts(1,10): error TS2305: Module 'ajv' has no exported member ...

Tips for implementing multiple yield generators in redux-saga

Our redux-saga generator has multiple yield statements that return different results. I am struggling with typing them correctly. Here's an illustration: const addBusiness = function* addBusiness(action: AddBusinessActionReturnType): Generator< ...

Exploring the functionalities of TypeScript's mapKey and pick features

I am looking to convert the JavaScript code shown below into TypeScript, but I don't want to use loadish.js. let claimNames = _.filter<string>(_.keys(decodedToken), o => o.startsWith(ns) ); let claims = <any>( _.mapKeys(_ ...

What is the best way to create a Material toolbar that changes from transparent to opaque when scrolling?

I'm in the process of familiarizing myself with Angular. I have incorporated Angular Material and I am trying to achieve a sticky and opaque material toolbar that becomes transparent with visible text when scrolling at the top of the page. Most soluti ...

I am facing an issue where the conversations entered by the user and those generated by the AI are not being stored in my Postgres database within my next.js application

Whenever a question is posed to the AI and a response is provided, the issue arises where the information is not getting saved in the database. Despite including console.log statements in the route.ts file indicating that messages from both the AI and th ...

Using Angular, you can effortlessly inject elements into the editable div from any location on the page

Currently, I am working on developing an HTML interface that allows users to input text and send it as a notification to our mobile application. However, I am encountering challenges with the text and dynamically inserted elements using Angular 5; The te ...

Using NodeJS to perform asynchronous tasks with setImmediate while also incorporating private class

Today marks my first time experimenting with setImmediate. I've come to realize that it may not be able to run private class methods. Can someone shed some light on this? Why is that the case? Not Functioning Properly When trying to use a private cl ...

Running a TypeScript program that has been compiled with module resolution is not working as expected

I am currently in the process of compiling a TypeScript file with the following code: import { magic } from 'lib/magic'; magic(); The file structure looks like this: ./src/ main.ts lib/ a/magic.ts b/magic.ts Within ...

Tips for evaluating modifications in state

In my TypeScript component, the code looks like this: import React, {useState} from 'react'; import {App} from "../../../../interfaces/interfaces"; import {map, find, filter} from "lodash"; import NavigationItem from "./N ...

What is the reason behind the never return value in this typescript template?

As demonstrated in this example using TypeScript: Access TypeScript Playground type FirstOrSecond<condition, T1, T2> = condition extends never ? T1 : T2 type foo = never extends never ? () => 'hi' : (arg1: never) => 'hi' ...

Guide to developing universal customized commands in Vue 3 using Typescript

I recently built my app using the Vue cli and I'm having trouble registering a global custom directive. Can anyone point out what I might be doing incorrectly here? import { createApp } from "vue"; import App from "./App.vue"; impo ...

Encountering an error in Cytoscape using Angular and Typescript: TS2305 - Module lacks default export

I am working on an Angular app and trying to integrate Cytoscape. I have installed Cystoscape and Types/cytoscape using npm, but I encountered an error when trying to import it into my project. To troubleshoot, I started a new test project before implement ...

What is the best way to make the current year the default selection in my Select control within Reactive Forms?

Hey there! I managed to create a select element that displays the current year, 5 years from the past, and 3 years from the future. But now I need to figure out how to make the current year the default selection using Reactive Forms. Any ideas on how to ac ...