1

I've been googling for hours, but can't wrap my head around this issue..

I have an interface:

interface Alert {
  uuid: string,
  city: string,
  address: number
}

and I would like to itterate over a object, which is for sure compliant with this interface:

 const alerts: Alert[] = response.from.api.alerts

now when I try to do something like this I get an error in ts compiler:

for (const alert of alerts) {
 for(const field in alert) {
   // this shows an error: Element implicitly has an 'any' type... ts(7053)
   console.log(alert[field])
 }
}

what's the correct way of accesing value of alert object?

should I use a type guard or something similar?

how can I declare to typescript that field is a keyof of alert?

should I disable the noImplicitAny flag -> is this considered "best practice"?

I know this question has been asked many times, but none of the answers helped me..

Thx for any help!

Janisko Psisko
  • 131
  • 1
  • 1
  • 8
  • Interface are not known at compile time, there could be anything in it. if you look at [this example](https://codesandbox.io/s/great-nobel-eh20l?file=/src/index.ts) you can put anything in it. Maybe [this answer](https://stackoverflow.com/a/45670960/9124424) could help? – Stutje Sep 30 '20 at 13:49
  • Please also note that `alert` is a blockscoped function. – MoxxiManagarm Sep 30 '20 at 13:56
  • 1
    I've reviewed this answers, but non of them are relevant to the question in hand. The first one is not using an interface and the second answer is about trying to get object keys from interface, which is nice, but not my question. I'm curious how to "dynamically" access a property in typescript object. accessing alert[field] is the trick I want to learn in a typescript world. There has to be a way how to do this.. – Janisko Psisko Sep 30 '20 at 13:57
  • @MoxxiManagarm could you please elaborate? – Janisko Psisko Sep 30 '20 at 13:58
  • @JaniskoPsisko open browsers console and call `alert(1)`. See what happens. – MoxxiManagarm Sep 30 '20 at 13:59
  • 1
    @Stutje Also, my error is not in compile time. I could pass through it with //@ts-ignore, but that's not the goal. I want to do this in some sort of "best practice" manner – Janisko Psisko Sep 30 '20 at 14:00
  • @MoxxiManagarm. I get your point. But the variable could be called "test", the error would be the same: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Alert'... – Janisko Psisko Sep 30 '20 at 14:01

3 Answers3

2

So, I found a way how to deal with this error (ts.7053).

First of all, thank you all for you help, but i belive my question has been misleading. Correct question should have been -> how to access typed object with bracket notation in for loop.

I've created a new interface called Indexable, which looks like this:

export interface Indexable {
  [key: string]: any;
}

my "Alert" interface is now extending this interface:

interface Alert extends Indexable {
  uuid: string,
  city: string,
  address: number
}

now I can do a for loop on an object which is typed as "Alert" without errors:

...
 for(const field in alert) {
   console.log(alert[field]
 }
...
Janisko Psisko
  • 131
  • 1
  • 1
  • 8
0

You can try something like this

    alerts.forEach( (field: Alert) => {
      console.log(field.uuid);
    });
Dharman
  • 30,962
  • 25
  • 85
  • 135
Simon
  • 1
  • Hi, I don't have issue accessing the direct field, i.e alert.uuid. Maybe the question I'm asking is not exact. I want to iterate in for in loop and to *dynamically* access a property of a object which is typed. – Janisko Psisko Sep 30 '20 at 13:54
0

In my opinion, if you have the interface and you are sure that the object is compliant with your interface, you shouldn't iterate over each key of the object. Underwood, typescript will believe you and assume that alerts is an array of Alerts.

So basically you could do something like this:

const alertsWithoutType: any = [
    { uuid: "uuid1", city: "city1", address: 1},
    { uuid: "uuid2", city: "city2", address: 2}
];

const alerts = alertsWithoutType as Array<Alert>;

alerts.forEach(alert => console.log(alert));

But, if you want to iterate over each key of your object, you should tell typescript that you don't know the object type:

const alerts: any = [
    { uuid: "uuid1", city: "city1", address: 1},
    { uuid: "uuid2", city: "city2", address: 2}
];

for (const alert of alerts) {
    Object.getOwnPropertyNames(alert)
        .forEach(key => 
            console.log(`key: ${key}, value: ${alert[key]}`)
        );
}
Ricardo Rocha
  • 14,612
  • 20
  • 74
  • 130
  • 3
    The thing which bugs me the most in your solution is the use of "any" keyword. Why should I use "any", if I know the type of the response? I'm not proficient in typescript, but imho this is an antipattern in typescript, which I try to use only as a last resort. – Janisko Psisko Sep 30 '20 at 14:26
  • @JaniskoPsisko You should avoid using `any`. If you know the type, you should type your variables. Saying that, for your case, if you know the interface, why you will want to iterate over each property type of the object? You can access explicit each property using the proper type name. That is the reason which typescript is retriving you an error. – Ricardo Rocha Sep 30 '20 at 14:32
  • There could be numerous reasons. The example I've provided in this question is trivial, but in the real world scenario I'm dealing with, I would like to coerce the item into a proper shape I need. i.e all strings in object should be uppercased. It's a legit thing to do something like `if(typeof alert[field] === 'string') let capitalizedVal = alert[field]. toUpperCase()` ... – Janisko Psisko Sep 30 '20 at 14:43