简体   繁体   中英

Pattern matching switch statements

Suppose I have APIs that return information about animals. However, the json payload varies greatly for each kind of animal, even if many properties are common and mandatory.

I want to have 'strongly typed' typescript class for each of these different animals so my code doesn't become a mess. Each animal requires very distinct and specific handling!

What is the right way of doing this? Basically I'd like to accomplish something like this:

interface Animal {
    Name: string;
    Weight: number;
}

interface Insect extends Animal {
    AmountOfEyes: number;
}

interface Bird extends Animal {
    PlumageColor : string;
}

function OnlyForBirds(bird: Bird)
{
     // do something birdly
}

function OnlyForInsects(insect: Insect)
{
     // do something creepy
}


function GetAnimal(animalId: string) : Promise<Animal>
{
    const uri = `${baseURL}/${animalId}`;

    // fetches the json response body from http request
    const result = await get<any>(uri); 

    switch(animal.Name)
    {
        case  'Insect':
            return result as Insect;
        case ...
            ...
    }

    // throw unhandled
}

function ProcessAnimal(animalId:string) : Promise
{
    let animal = await GetAnimal(animalId);
 
    // how do I do this now? Can't I use something over tye interface
    // types instead of using the .Name and casting again?
    // is there any advisable standard I can use?

    if(animal is a bird){  
        OnlyForBirds(bird)
    }

    else if(animal is an insect){
        OnlyForInsects(insect)
    }
}

Any suggestions including not using interfaces like this are appreciated.

For your use case, the answer that you posted is probably the best solution. I just want to chime in with a different approach. Your solution starts to break down if you want to have multiple layers of inheritance, where Duck extends Bird . If you want to find if an Animal matches the base Bird interface, you can define a custom type guard function that looks at the properties of the object to see if it has a PlumageColor . If it does, then typescript knows that it's ok to use it as a Bird .

Here is the basic version. We say that animal has an optional property PlumageColor so that we can access it without error even when it's undefined . Then we check that PlumageColor is defined and it's a string .

const isBird = (animal: Animal & {PlumageColor?: any}): animal is Bird => {
  return typeof animal.PlumageColor === "string";
}

This version with generics is better because it asserts that animal is Bird while also preserving any other type information that was already know about the animal .

const isBird = <T extends Animal & {PlumageColor?: any}>(animal: T): animal is T & Bird => {
  return typeof animal.PlumageColor === "string";
}

I figured it out. Some deep dark magic. Define an enum proprery in the base interface. Each new different type of class, or animal in this case, assigns itself one of these properties. Switch on this property and within the switch you have your typed animal

enum AnimalType {
    Insect = "Insect",
    Bird = "Bird"
}

interface Animal {
    Type: AnimalType;
    Weight: number;
}

interface Insect extends Animal {
    Type: AnimalType.Insect; // MAGIC BREWING
    AmountOfEyes: number;
}

interface Bird extends Animal {
    Type: AnimalType.Bird; // MAGIC BREWING
    PlumageColor : string;
}

function OnlyForBirds(bird: Bird)
{
     // do something birdly
}

function OnlyForInsects(insect: Insect)
{
     // do something creepy
}


function GetAnimal(animalId: string) : Promise<Animal>
{
    const uri = `${baseURL}/${animalId}`;

    // fetches the json response body from http request
    const result = await get<any>(uri); 

    switch(animal.Type)
    {
        case  'Insect':
            return result as Insect;
        case ...
            ...
    }

    // throw unhandled
}

function ProcessAnimal(animalId:string) : Promise
{
    let animal = await GetAnimal(animalId);

    switch(animal.AnimalType){
        case AnimalType.Insect:
            OnlyForInsects(animal); // within this case clause, animal is typed as an Insect! MAGIC!
            break;
        
        case AnimalType.Bird:
            OnlyForBirds(animal); // within this case clause, animal is typed as an bird! Go free little bird!
            break;

        default:
            // throw
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM