1

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.

2
  • Please share reproducible example. individualQuantity and individualCartCountResponseParameter are not declared Commented Dec 10, 2021 at 8:34
  • Try to use const individualParameters= ["appleQuantity", "bananaQuantity", "pearQuantity"] as const instead const individualParameters: string[] = ["appleQuantity", "bananaQuantity", "pearQuantity"] Commented Dec 10, 2021 at 8:40

4 Answers 4

2

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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

P.S. According to TS convention, types should be capitalized.

5 Comments

Or simply change the parameters declaration to const individualParameters: (keyof response)[] = ["appleQuantity", "bananaQuantity", "pearQuantity"];, right?
@jboot you are right
@jboot you can post it as a separate answer
I did, thanks. I referred to your answer as well, because I think you already covered most of it and it's still a valid answer.
@captain-yossarian thx for your detailed explanation! But i will accept answer from jboot as a cleaner solution
0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.