Code branching based on types of interfaces that extend

116 views Asked by At

I have some interfaces that extend from a base:

enum eItemType { /* … */ }

interface Item {
    someCommonKeyValPairCollection: eItemType;
    type: eItemType;
    weight: number;
    // …
}

interface ItemTypeA extends Item {
    someKeyValPairCollection: eItemType;
}

interface ItemTypeB extends Item {
    someOtherKeyValPairCollection: eItemType;
}

type tItem = Item | ItemTypeA | ItemTypeB

And then I have a method that receives an Item, but it could be a (non-keyword) generic Item or ItemTypeA or ItemTypeB, so I created the union type tItem. The method does some common things, and then some specific things based on item.type (looping through either someKeyValPairCollection or someOtherKeyValPairCollection. Typescript freaks out, throwing half a dozen assignment errors, ex Property 'someKeyValPairCollection' is missing in type Item (no shit).

The method looks roughly like this:

processItem(item: tItem): boolean {
    const isValid: boolean = checkValidity(item);
    if (!isValid) return false;

    _.each(someCommonKeyValPairCollection, () => { /* do some common stuff */ });

    // if (typeof item.someKeyValPairCollection !== 'undefined') // tried first, doesn't like
    if (item.type === eItemType.a) processA(item.someKeyValPairCollection);

    return true;
}

How is one supposed to handle if (item.type === eItemType.a) // do some special stuff?

1

There are 1 answers

6
Titian Cernicova-Dragomir On BEST ANSWER

You need to use a discriminated union. A union where one property ( type in this case) has a specific value for a particular type. You can read more about the topic here

enum eItemType {
    a, b, c
}

interface Item {
    someCommonKeyValPairCollection: eItemType;
    type: eItemType;
    weight: number;
    // …
}
interface ItemTypeA extends Item {
    type: eItemType.a
    someKeyValPairCollection: eItemType;
}

interface ItemTypeB extends Item {
    type: eItemType.b
    someOtherKeyValPairCollection: eItemType;
}

interface ItemTypeOther extends Item {
    type: Exclude<eItemType, eItemType.a | eItemType.b>
}


type tItem = ItemTypeA | ItemTypeB | ItemTypeOther
function processItem(item: tItem): boolean {
    if (item.type === eItemType.a) {
        item.someKeyValPairCollection
    }
    else if (item.type === eItemType.b) {
        item.someOtherKeyValPairCollection
    } else {
        item  // will be ItemTypeOther
    }
    return true;
}

Edit

Added a simple alternative to have a catch the rest of the enum values without having to define a separate type for each enum value or having to list them individually.