141

I have the follow setup and when I loop through using for...of and get an error of :

Property "country" doesn't exist on type "object".

Is this a correct way to loop through each object in array and compare the object property value?

let countryProviders: object[];

export function GetAllProviders() {
   allProviders = [
      { region: "r 1", country: "US", locale: "en-us", company: "co 1" },
      { region: "r 2", country: "China", locale: "zh-cn", company: "co 2" },
      { region: "r 4", country: "Korea", locale: "ko-kr", company: "co 4" },
      { region: "r 5", country: "Japan", locale: "ja-jp", company: "co 5" }
   ]

   for (let providers of allProviders) {
      if (providers.country === "US") { // error here
         countryProviders.push(providers);
      }
   }
}
Boussadjra Brahim
  • 82,684
  • 19
  • 144
  • 164
Jason
  • 1,680
  • 3
  • 13
  • 16

4 Answers4

148

You probably have allProviders typed as object[] as well. And property country does not exist on object. If you don't care about typing, you can declare both allProviders and countryProviders as Array<any>:

let countryProviders: Array<any>;
let allProviders: Array<any>;

If you do want static type checking. You can create an interface for the structure and use it:

interface Provider {
    region: string,
    country: string,
    locale: string,
    company: string
}

let countryProviders: Array<Provider>;
let allProviders: Array<Provider>;
Saravana
  • 37,852
  • 18
  • 100
  • 108
  • Why you didn't use a class instead an interface? – Rafael Reyes Mar 01 '18 at 20:47
  • 12
    @RafaelReyes If all you need is compile-time type checking on your objects, you can use interfaces (interfaces are erased during build and don't affect the generated JS). Classes however do get converted to JS. Based on the question there was no need for a class to be created. – Saravana Mar 02 '18 at 01:16
  • 1
    @Saravana how would you handle this issue if you are using third party package. I am using JSforce and trying to use "search" function on the object but it is throwing "Property 'search' does not exist on type 'Connection'"...any clues? – user269867 May 23 '18 at 21:34
  • @user269867 Then it is probably a case of the type definitions not updated with the library. You can either add the method yourself and raise a PR [in the DefinitelyTyped repo](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jsforce/connection.d.ts) for a permanent fix (this will take some time). Or if you want to fix this temporarily, you can add properties to declarations [as described here](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) – Saravana May 24 '18 at 01:06
  • But property country does exist on object - it's a string, JS object keys are strings. How can I tell TS to allow me to access an object like it's an object? – mikemaccana Jun 17 '20 at 12:43
  • @mikemaccana then you should be using [index types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types-and-index-signatures) (or disable static typing with `any`) which support arbitrary keys, not `object`. – Saravana Jun 17 '20 at 13:36
  • @Saravana But that's what `object` is for in JS. Surely TS supports using `object` to do what `object` has always done? Isn't the whole point of TS that existing JS should work? – mikemaccana Jun 17 '20 at 18:26
  • @mikemaccana Think of `object` here as `System.Object` in C# or its equivalent in Java or other language. You can use the type to store any value, but to access its properties, you need to cast it to a specific type. And `object` is in no way a JS construct, it is a TS static type to indicate any non-primitive value. If you wan JS-like flexibility, then you are really looking for dynamic typing (which is what `any` offers). And your assertion is wrong that all valid JS code works with TS. [See this](https://stackoverflow.com/questions/29918324/is-typescript-really-a-superset-of-javascript) – Saravana Jun 17 '20 at 19:58
  • Thanks @Saravana. That link explains a lot. – mikemaccana Jun 17 '20 at 22:08
  • 1
    Let's not teach people to use `any`. It turns off type checking completely (which rather is not what they want if they're using TS in the first place) and is very hard to unwind if other code starts depending on the fact that there's some `any`. Just type `countryProviders` according to the shape its elements has: 4 keys it all it's got, from what I see. Or use the `Record` type if that's not the case. – jalooc Apr 12 '22 at 08:55
52

If your object contains any key/value pairs, you could declare an interface called keyable like :

interface keyable {
    [key: string]: any  
}

then use it as follows :

let countryProviders: keyable[];

or

let countryProviders: Array<keyable>;

For your specific case try to define the key type and use Record utility to define the object :

type ProviderKey="region" | "country" | "locale" | "company"

let countryProviders: Array<Record<ProviderKey,string>>;

RajeshKdev
  • 6,365
  • 6
  • 58
  • 80
Boussadjra Brahim
  • 82,684
  • 19
  • 144
  • 164
14

Yes the above methods are highly recommended, but if you have no choice and want to continue using "allProviders" as an array of object, then go for this method. This worked for me without creating an interface.

if(providers["country"] === "US") // replacement
  • 2
    How the field is accessed is unrelated to the issue: TypeScript playground – https://www.typescriptlang.org/play?ts=4.3.5#code/MYewdgzgLgBAhgLhiARgKwKbFgXhgbwF8AoUSWABwAYY84A6CEAWwwAUAnEC2nPAAwwcuHXjE7chUAJ4wA5E1YSKcmABMQGCDDAhYGAB4BLaMjAwZFDPNSZsc-gG5S4UxQCMteAG0FLdlwqALq8AkIiYspSsn5KgaoaWjp6MIYmsOAW0lY26FhQDs5AA – tiffon Aug 04 '21 at 02:16
0

Yes you should try to avoid object, any etc. if you can, after all the thing is called TypeScript for a reason.

If, however, you need a working dirty trick:

let o: object = { id: 1 }
console.log(o.id);                // throws error
console.log((o as any).id);       // fine

As always: anything goes with any.

Marcel
  • 568
  • 7
  • 12