Typescript Union Types: Dealing with Interfaces

I know this question is very old, but I was playing around with the same problem, as I was learning the difference between | and & when making Type Unions.

there are some options to solve this problem (that is also linter-friendly). The best way is to use a discriminator in all your interfaces (a narrow interface).

//First create a super-interface that have the discriminator
interface B
{
    kind:'b1'|'b2' //define discriminator with all the posible values as string-literals (this is where the magic is)
}

interface B1 extends B
{
    kind: 'b1' //now narrow the inherited interfaces literals down to a single 
    //after that add your interface specific fields
    data1: string;
    data: string;
}

interface B2 extends B
{
    kind:'b2' //now narrow the inherited interfaces literals down to a single
    //after that add your interface specific fields
    data2: string;
    data: string;
}

//lets initialize 1 B1 type by using the literal value of a B1 interface 'b1'
var b1: B1|B2 = {
    kind:'b1',
    data: 'Hello From Data',
    data1:'Hello From Data1'
    //typescript will not allow you to set data2 as this present a B1 interface
}
//and a B2 Type with the kind 'b2'
var b2: B1|B2 = {
    kind: 'b2',
    data: 'Hello From Data',
    //typescript will not allow you to set data1 as this present a B2 interface
    data2: 'Hello From Data2'
}

another option is to check for fields on an object using the "in"-keyword, but this can cause a lot of boilerplate code, as you have to update it every time you change your interface.

interface B1
{
    data1: string;
    data: string;
}

interface B2
{
    data2: string;
    data: string;
}

var b3: B1|B2 = {
    data: 'Hello From Data',
    data1:'Hello From Data1',
    data2:'Hello From Data2'
}

console.log(b3.data); //this field is common in both interfaces and does not need a check

if('data1' in b3) //check if 'data1' is on object
{
    console.log(b3.data1);
}
if('data2' in b3){
    console.log(b3.data2); //check if 'data2' is on object
}

TypeScript interfaces only exist at compile-time, so there isn't much you can do to test for interface types at run-time. The code you specified in your question makes sense and is probably your best option.

However, if you have the flexibility to change your interfaces to classes, you can use TypeScript's type guards to do more elegant type checking:

class DescriptionItem {
    Description: string;
    Code: string;
}
class NamedItem {
    Name: string;
    Code: string;
}

function MyLogic(i: DescriptionItem | NamedItem) {
    let desc: string;
    if (i instanceof DescriptionItem) {
        desc = i.Description;
    } else {
        desc = i.Name;
    }

    return i.Code + ' - ' + desc;
}

Tags:

Typescript