In TypeScript, how can I check the type of an object, and branch off based on that type?
const createAnimalForId(animalId: AnimalIdsEnum): CatAnimal | BirdAnimal {
// animalData has type: CatAnimalData | BirdAnimalData | undefined
const animalData = animalsMap.get(animalId);
// Q: How can I check the type here?
if(animalData "is of type" CatAnimalData) {
// NB: I want it to be aware that animalData here is of type CatAnimalData
return new CatAnimal(animalData);
}
// Q: How can I check the type here?
else if(animalData "is of type" BirdAnimalData) {
// NB: I want it to be aware that animalData here is of type BirdAnimalData
return new BirdAnimal(animalData);
}
else {
throw new RangeError("Animal type not recognised");
}
}
NB: CatAnimal
and BirdAnimal
both inherit from AbstractAnimal
.
CodePudding user response:
how can I check the type of an object?
Since these types are implemented as class
es, you can just use instanceof
:
if (animalData instanceof CatAnimalData) {
…
}
if (animalData instanceof BirdAnimalData) {
…
}
… and branch off based on that type?
Don't. This is an ugly, non-extensible design. Instead, make all your inputs implement a toAnimal
or getAnimal
or createAnimal
method, and then just call that:
export class AbstractAnimalData {
name: string;
abstract createAnimal(): Animal {
throw new RangeError("Animal type not recognised");
}
}
export class CatAnimalData extends AbstractAnimalData {
favouriteFood: string;
// NB: Leaving out the constructor for brevity
createAnimal(): CatAnimal {
return new CatAnimal(this);
}
}
export class BirdAnimalData extends AbstractAnimalData {
archEnemy: string;
// NB: Leaving out the constructor for brevity
createAnimal: BirdAnimal {
return new BirdAnimal(animalData);
}
}
then
function createAnimalForId(animalId: AnimalIdsEnum): Animal {
const animalData = animalsMap.get(animalId);
if (!animalData) {
throw new RangeError(`AnimalId '${animalId}' not supported`);
}
return animalData.createAnimal();
// ^^^^^^^^^^^^^^^
}
CodePudding user response:
You can check if property in exists in animalType
const createAnimalForId(animalId: AnimalIdsEnum): CatAnimal | BirdAnimal {
// animalData has type: CatAnimalData | BirdAnimalData | undefined
const animalData = animalsMap.get(animalId);
if('favouriteFood' in animalData) {
// CatAnimalData
return new CatAnimal(animalData);
}
else if("archEnemy" in animalData) {
//BirdAnimalData
return new BirdAnimal(animalData);
}
else {
throw new RangeError("Animal type not recognised");
}
}
CodePudding user response:
One solution I can think of would be the following. However, this has the problem that, within the conditional cases, TypeScript is not aware of the specific type of animalData
.
As far as I understand, this means I would have to manually cast it, which I don't think is ideal.
const createAnimalForId(animalId: AnimalIdsEnum): CatAnimal | BirdAnimal {
// animalData has type: CatAnimalData | BirdAnimalData | undefined
const animalData = animalsMap.get(animalId);
if(animalData.type === animalType.catAnimal) {
// Problem: TypeScript is not aware of the specific type of animalData here
return new CatAnimal(
animalData // as CatAnimalData
);
}
else if(animalData.type === animalType.birdAnimal) {
// NB: I want it to be aware that animalData here is of type BirdAnimalData
return new BirdAnimal(
animalData // as BirdAnimalData
);
}
else {
throw new RangeError("Animal type not recognised");
}
}
CodePudding user response:
Use an instanceof
type guard:
const createAnimalForId(animalId: AnimalIdsEnum): CatAnimal | BirdAnimal {
// animalData has type: CatAnimalData | BirdAnimalData | undefined
const animalData = animalsMap.get(animalId);
if(animalData instanceof CatAnimalData) {
// animalData here is of type CatAnimalData
return new CatAnimal(animalData);
}
else if(animalData instanceof BirdAnimalData) {
// animalData here is of type BirdAnimalData
return new BirdAnimal(animalData);
}
else {
throw new RangeError("Animal type not recognised");
}
}