What is the best way to perform type checking for a basic generic function without resorting to using a cumbersome cast

Struggling with TypeScript and trying to understand a specific issue for the past few days. Here is a simplified version:

type StrKeyStrVal = {
    [key: string]: string
};

function setKeyVal<T extends StrKeyStrVal>(obj: T, key: keyof T, value: string) {
    obj[key] = value;
}

Essentially, I have objects containing only string keys and values. I am attempting to create a generic function that can assign any string key on an object to any string value. Seems straightforward, but TypeScript (strict mode) raises errors regarding the assignment obj[key] = value. This seems to be related to changes made after this pull request: https://github.com/microsoft/TypeScript/pull/30769

This code worked fine before TypeScript 3.5, but it was considered unsafe. However, given that my object can only have string keys and values, T extends this type, obj is of type T, key is a keyof T, and value is a string - how can this be deemed as unsafe? What can I do to demonstrate to TypeScript that this is safe without compromising type safety?

Answer №1

For similar reasons, this scenario is deemed unsafe as described here: the caller selects the type parameter, which could potentially be more restrictive than the generic restriction. This means that a type Shirt defined as:

type Shirt = { size: "S" | "M" | "L", color: string }
const redShirt: Shirt = { size: "L", color: "red" };

is considered a subtype of StrKeyStrVal:

const strKeyStrVal: StrKeyStrVal = redShirt; // valid

This poses an issue since the size property is more specific than string, allowing for unintended assignments like:

setKeyVal(redShirt, "size", "XXXXXL"); // whoops

So what are your options? If you're willing to take the risk, you can use a type assertion. Otherwise, a safer alternative would be refactoring.


One refactoring method just as unsafe as an assertion is removing generics entirely:

function setKeyVal(obj: StrKeyStrVal, key: string, value: string) {
  obj[key] = value; // valid
}
setKeyVal(redShirt, "size", "XXXXXL"); // yikes!

This approach is considered risky due to TypeScript's lack of soundness in non-generic supertype property writing scenarios. The conclusion being that prioritizing productivity over type safety is preferred by many TypeScript users. Perhaps that suits your needs as well.


A more secure way to refactor involves making the function sufficiently generic to only accept appropriate strings for each key:

  function setKeyVal<T extends StrKeyStrVal, K extends keyof T>(obj: T, key: K, value: T[K]) {
    obj[key] = value; // now safe
  }

This implementation is now seen as safe, with erroneous calls like the one involving redShirt resulting in errors at the call site:

  setKeyVal(redShirt, "size", "XXXXXL"); // error!
  // -----------------------> ~~~~~~~~
  // Argument of type '"XXXXXL"' is not assignable to parameter of type '"S" | "M" | "L"'

This method ensures the function is less prone to misuse but also makes it more difficult to use and annotate. It offers increased type safety with some trade-offs.


Your choice of approach may depend on your specific use cases and priorities. Regardless, I hope this sheds light on the situation. Best of luck!

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

Classifying Union Types based on their distinct characteristics

There is a specific type with its own unique property (method) type Functions = { method: "connect", request: number, response: number, } | { method: "remove", request: string, response: string, } I aim to create a function that can handle inp ...

Typescript threw an error stating "Cannot access properties of an undefined object" in the React-Redux-axios

As a backend developer, I am not very familiar with frontend development. However, for my solo project, I am attempting to create some frontend functionalities including user login right after setting the password. Below is the code snippet from UserSlice. ...

Utilizing Session storage throughout an Angular 2 application

I currently store a session variable as a JSON value in my home.component.ts. This variable needs to be accessed in various locations within the application. This is my code snippet: .do(data => sessionStorage.setItem('homes', JSON.stringif ...

The process of invoking the parent class's Symbol.iterator function from the child class's Symbol.iterator can be achieved by following a specific

I have two TypeScript classes defined below. class BaseIter { constructor(public a: number, public b: number, public c: number, public d: number){} *[Symbol.iterator](): Iterator<number> { yield this.a yield this.b yield this.c y ...

Angular function implementing a promise with a return statement and using the then method

I have a function in which I need to return an empty string twice (see return ''. When I use catch error, it is functioning properly. However, I am struggling to modify the function so that the catch error is no longer needed. This is my current ...

Unable to refresh the context following a successful API call

My current project in NextJS requires a simple login function, and I have been attempting to implement it using the Context API to store user data. However, I am facing an issue where the context is not updating properly after fetching data from the back-e ...

TypeScript typings for generic currying functions

I'm struggling to improve my skills in functional programming, particularly when dealing with typing generic "curry" functions. One example is a "Curry" version of the reduce function that I've written: const reduce = <S, R>(fn: (result: R ...

How to add a service to a static function in Angular

After incorporating a logger service into my project, I have encountered an issue with using it in NGXS static selectors. The selectors in NGXS are static methods, which prevent me from accessing the logger service injected via Angular DI. Are there any e ...

What is the reason behind the restriction on using 'this' on the left side of an assignment?

Within the component class, I've been working on this: export class myapp { detail; myarr = ['me', 'myself', 'i']; title = this.myarr[0]; this.detail = this.title ; //error } I'm curious why `this.detail` ...

Where can I find the @types for a specific lodash package?

Seeking to utilize a specific function from lodash - assignin. I have successfully installed lodash.assignin and incorporated it into my project: import assignIn = require('lodash.assignin'); However, when compiling, an error occurs: "error TS2 ...

The best way to access the value of a fulfilled Promise while debugging

I've been using this loopback application in my IntelliJ IDE. I set a breakpoint at the promise in calculator.controller.ts, where the code looks like this: @get('/multiply/{intA}/{intB}') async multiply( @param.path.integer('in ...

Encountering the error "No overload matches this call" while utilizing useQuery in TypeScript may indicate a mismatch in function parameters

My issue lies with TypeScript and types. Here is the API I am working with: export const clientAPI ={ //... getOptions: async (myParam: number) => get<{ options: Options[]; courses: any[] }>(`/courses?myParam=${myParam}`)().then((result) =& ...

What are the steps for transforming my 2D array to fit a specific schema using RxJS?

UPDATE I stumbled upon a potential solution that I have appended to my question and am now seeking a more refined approach. In the context of an Angular 9 application, I am working with a two-dimensional array that I need to restructure. Through my use of ...

Tips for accessing and modifying local files in Angular 2

Is there a method in Angular 2 to access files from an absolute path? I have utilized the 'filesaver' library for file saving, storing the files locally in txt/json formats. For instance: let blob = new Blob([document.getElementById(&apos ...

Utilizing TypeScript with React to dynamically select which component to render

My task seemed simple at first: to render a React component based on its name/type. Here is an example of how it is used: // WidgetsContainer.ts // components have a difference in props shape! const componentsData = [ { type: 'WIDGET_1', ...

Event triggered by clicking on certain coordinates

Just starting with @asymmetrik/ngx-leaflet and Angular, so this might be a beginner's issue... I'm working on an Angular.io (v5) project that incorporates the @asymmetrik/ngx-leaflet-tutorial-ngcli Currently, I'm trying to retrieve the coo ...

A step-by-step guide on incorporating a .env file into a React JS project that is written with TypeScript

I'm currently working on a React JS project with TypeScript. I know that we can utilize a .env file to define some configurations, like so: .env file REACT_APP_SOME_CONFIGURATION = "some value" We can then use these configurations in our c ...

I am attempting to incorporate an NPM package as a plugin in my Next.js application in order to prevent the occurrence of a "Module not found: Can't resolve 'child_process'" error

While I have developed nuxt apps in the past, I am new to next.js apps. In my current next.js project, I am encountering difficulties with implementing 'google-auth-library' within a component. Below is the code snippet for the troublesome compon ...

What causes the return value type in a functional interface to be loosely implemented in Typescript?

In an attempt to explain a specific partial type of return value for a functional interface, I have encountered an issue. Within my IStore interface, there is only one property called test. When assigning this interface to the function foo, which returns ...

Perform the subtraction operation on two boolean values using Typescript

I'm working with an array: main = [{ data: x, numberField: 1; }, { data: y, numberField: 2; }, { data: x, numberField: 3; }, { data: z, numberField: 4; }, { data: ...