In Typescript, there is no guarantee that all properties will be of type Cat
within the object cats
. The interface may define fewer properties than what actually exist at runtime, and their types remain unknown until then.
To ensure the type Cat
, you have three options: Type casts, type guards, and hashmap types.
Type Casts
If you are confident that all properties in cats
are of type Cat
, you can cast the result directly:
let cats: Zoo; // assuming correct initialization
for (const i in cats) {
if (cats.hasOwnProperty(i)) {
const creature = cats[i] as Cat;
// 'creature' is now of type 'Cat'
}
}
Type Guards
When uncertain about property types in cats
, a type guard can help filter out values with the correct type:
// Define type guard
function isCat(value: any): value is Cat {
return value.hasOwnProperty('meow');
}
//...
let cats: Zoo; // assuming correct initialization
for (const i in cats) {
const creature = cats[i];
if (cats.hasOwnProperty(i) && isCat(creature)) {
// 'creature' is treated as 'Cat' within this block
}
}
Hashmap Types
To allow an arbitrary number of entries of type Cat
, consider using a hashmap type instead of the Zoo
interface:
type Zoo = { [key: string]: Cat };
let cats: Zoo; // assuming correct initialization
for (const i in cats) {
const creature = cats[i];
// 'creature' is of type 'Cat'
}
An issue with this approach is the inability to specify specific property names like in interfaces. TypeScript 2.2 does not allow syntax such as:
type Zoo = { ["bobtail" | "bengal" | "cheetoh"]: Cat };
Despite this limitation, the hashmap type inference eliminates the need for additional steps such as casts or type guards.