Reputation: 2429
I am trying to convert a switch statement to object literal in typescript but am getting the following error:
Argument of type 'AllowedAnimals' is not assignable to parameter of type 'never'.
The intersection 'Dog & Horse' was reduced to 'never' because property 'species' has conflicting types in some constituents.
Type 'Dog' is not assignable to type 'never'.
... with the example code is below. Am I doing my typings incorrectly or is this anti-pattern with Typescript?
interface Animal{
species:string,
weight:number,
name:string
}
type AllowedAnimals = Dog |Horse
interface Dog extends Animal{
species:"dog",
hasFleas:boolean
}
interface Horse extends Animal{
species:"horse",
handsTall:number
}
const processDog = (d:Dog)=>{}
const processHorse = (h:Horse)=>{}
const userInput = "";
const a:AllowedAnimals = JSON.parse(userInput);
switch (a.species){
case "dog":
processDog(a);
break;
case "horse":
processHorse(a);
break;
default:
break;
}
const objectLiteral = {
"dog":processDog,
"horse":processHorse
}
objectLiteral[a.species](a); //<- Error
Upvotes: 1
Views: 2266
Reputation: 664936
Typescript isn't clever enough to figure out that the type of objectLiteral[a.species]
is dependent on the concrete type of a
. All it knows that the property access will come up with (d: Dog) => void | (h: Horse) => void
, but when you try to call that it will need to unify the argument types, arguing that the argument would need to have a type that can be passed to any of these functions. (a: Dog & Horse) => void
comes up as a: never
, as the error message explains.
You can cheat however with an explicit cast:
(objectLiteral[a.species] as (a: AllowedAnimal) => void)(a);
That way you can satisfy the typechecker. Whether it's a good idea I can't tell - you probably get less complete checks than with switch
.
type process<X extends Animal> = (a: X) => void
const processDog: process<Dog> = _=>{}
const processHorse: process<Horse> = _=>{}
const objectLiteral: {
[p in AllowedAnimals['species']]: process<AllowedAnimals & {species: p}>
} = {
"dog": processDog,
"horse": processHorse
};
let a: AllowedAnimals = null as any;
(objectLiteral[a.species] as process<AllowedAnimals>)(a);
Upvotes: 3
Reputation: 3253
As I said in my comment:
Typescript is doing fine here. a
is a value which you're telling typescript is of type AllowedAnimals
which itself is a union. when you write objectLiteral[a.species]
typescript doesn't know which one between dog
or horse
would be indexed so it can't give you a hint about the function argument.
As you have mentioned in your question, you could benefit from type narrowing using switch statements to do what you're trying to do:
interface Animal{
species:string,
weight:number,
name:string
}
type AllowedAnimals = Dog |Horse
interface Dog extends Animal{
species:"dog",
hasFleas:boolean
}
interface Horse extends Animal{
species:"horse",
handsTall:number
}
const processDog = (d:Dog)=>{}
const processHorse = (h:Horse)=>{}
const process = (animal : AllowedAnimals) => {
switch(animal.species) {
case "dog":
objectLiteral[animal.species](animal);
break;
case "horse":
objectLiteral[animal.species](animal);
break;
}
}
const objectLiteral = {
"dog":processDog,
"horse":processHorse
}
const userInput = "";
const a:AllowedAnimals = JSON.parse(userInput);
process(a);
Upvotes: 2