简体   繁体   中英

TypeScript Unsafe use of expression of type 'any' ERROR

I am new to TypeScript and describe my scenario as follow:

I have a type like this:

 type response = {
  totalQuantity: number;
  numberOfCatogory: number
  appleQuantity: number;
  bananaQuantity: number;
  pearQuantity: number;
};

There is validation on each individual fruit quantity, in this case are appleQuantity, bananaQuantity, and pearQuantity, which means I need to get each individual fruit quantity from response.

So I created a string array which saves individual property key like this:
const individualParameters: string[] = ["appleQuantity", "bananaQuantity", "pearQuantity"]

Function logic like this:

for (const individualParameter of individualParameters) {
    let individualQuantity = response[individualParameter];
    ...
}

But when I build, TSLint throws error at response[individualParameter] saying

Unsafe use of expression of type 'any'

I think it is because response can't recognize type from string array element?
I am new to TypeScript and curious of a good way to solve this problem.

Problem is in this line: const individualParameters:string[] = ["appleQuantity", "bananaQuantity", "pearQuantity"] .

According to type definition of individualParameters each element is a string . It means that even this "foo" string is assignable to type string .

Hence, typescript rejects using response[individualParameter] because response is a type with strict keys whereas individualParameter might be any string.

In order to make it work, you can use const assertion :

type response = {
  totalQuantity: number;
  numberOfCatogory: number
  appleQuantity: number;
  bananaQuantity: number;
  pearQuantity: number;
};

const individualParameters = ["appleQuantity", "bananaQuantity", "pearQuantity"] as const

declare const response: response;

for (const individualParameter of individualParameters) {
  let individualQuantity = response[individualParameter]; // ok

}

If you don't like as const approach, you can use extra function:

type response = {
  totalQuantity: number;
  numberOfCatogory: number
  appleQuantity: number;
  bananaQuantity: number;
  pearQuantity: number;
};


declare const response: response;

const iterator = <
  Obj extends Record<string, number>,
  Keys extends Array<keyof Obj>
>(record: Obj, keys: Keys) => {
  for (const individualParameter of keys) {
    let individualQuantity = record[individualParameter];
  }
}

iterator(response, ['foo']) // expected error

// works only with literal array
iterator(response, ["appleQuantity", "bananaQuantity", "pearQuantity"]) // ok

Playground

Obj - represents response argument, Keys - makes sure that second array argument consists of valid Obj keys

PS According to TS convention, types should be capitalized.

So I think most people already explained what's going on and I think my personal favourite answer thus far is captain-yossarian's answer . He explained the problem very well:

Hence, typescript rejects using response[individualParameter] because response is a type with strict keys whereas individualParameter might be any string.

This issue, however, can easily be resolved by telling Typescript that you're defining an array containing strings that are also keys of the type you're trying to access:

type response = {
  totalQuantity: number;
  numberOfCatogory: number
  appleQuantity: number;
  bananaQuantity: number;
  pearQuantity: number;
};

// this should be your non-null and defined object in reality
let response: response;

// just this one line below was altered
const individualParameters: (keyof response)[] = ["appleQuantity", "bananaQuantity", "pearQuantity"];

for (const individualParameter of individualParameters) {
    let individualQuantity = response[individualParameter];
}

The type (keyof response)[] means you'll always have an array with just the keys of a response type. In my example this declaration is redundant as the compiler can already infer that all properties you defined are keys of the type, but this is a different story when the for loop is in a function, for instance. I assume this is the case for you, otherwise you wouldn't be asking this question in the first case.

And @captain-yossarian is right; According to TS convention, types should be capitalised.

You need to make your response indexable. More information about that can be found here .

In your case it can be done by using an Interface like so:

interface IResponse {
  [key: string]: number;  // <- This line makes your interface indexable
  totalQuantity: number;
  numberOfCatogory: number
  appleQuantity: number;
  bananaQuantity: number;
  pearQuantity: number;
}

Now you just need to make sure your response implements the IResponse interface:

const response: IResponse = {
  totalQuantity: 1,
  numberOfCatogory: 2,
  appleQuantity: 3,
  bananaQuantity: 4,
  pearQuantity: 5
}

After this you are able to use your string array for getting individual fruit quantities like so:

const individualParameters: string[] = ["appleQuantity", "bananaQuantity", "pearQuantity"];

for (let individualParameter of individualParameters) {
    console.log(response[individualParameter]);
}

// Output:
// 3
// 4
// 5

One simple way is that you can choose to make your type's key as string variable

type response  = {
    [key: string]: number;
};

But if you really to be fix key to be appleQuantity, bananaQuantity, blablabla you can try out Mapped Type , click this link for more details https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM