How to check if property exists if obj is one of types
Asked Answered
S

2

9

Let's say I have few interfaces A, B, C implementing common Base.

interface Base {
    x: number;
    y: number;
    z: number;
}

interface A extends Base {
    a: true;
}

interface B extends Base {
    b: true;
}

interface C extends Base {
    C: true;
}

And function with if statements:

function foo(arg: A|B|C){
    if(arg.a!==undefined){//throws type error
        //do stuff for type a
    } else if(arg.b !== undefined){//throws type error
        //do stuff for type b
    } else if(arg.c !== undefined){ //throws type error
        //do stuff for type c
    }
}

How to correctly check if property exists? I don't wan't to use any type. Is //@ts-ignore only option?

Steels answered 11/4, 2019 at 20:44 Comment(3)
do if(arg typeOf A){...} this also tells the compiler which type your workign with aloowing you to autocomplete the attributes (if im confusing languages then it was instanceOf)Progression
@samuel-liew My answer got deleted for being a duplicate (I mistakenly posted it to another answer but deleted it there) Not sure how to appeal such a deletionMicmac
Looks like the post got undeleted.. the other answer is good too :)Micmac
K
5

You can use a type guard:

function isA(arg: A | B | C): arg is A {
    return (<A>arg).a !== undefined;
}

function isB(arg: A | B | C): arg is B {
    return (<B>arg).b !== undefined;
}

function foo(arg: A | B | C) {
    if (isA(arg)) {
        // do stuff for type a
    } else if (isB(arg)) {
        // do stuff for type b
    } else {
        // do stuff for type c
    }
}
Kelsey answered 11/4, 2019 at 20:50 Comment(2)
Custom type guard is overkill in this case IMOMicmac
In my case it will be enaugh to use second answer, but this is really helpful.Steels
M
19

Typescript will only allow access to common properties. Since the properties you test are not common to all members of the union, typescript will not let you access them.

You can use an in type guard instead to test for the presence of the property.

interface Base {
    x: number;
    y: number;
    z: number;
}

interface A extends Base {
    a: true;
}

interface B extends Base {
    b: true;
}

interface C extends Base {
    C: true;
}

function foo(arg: A|B|C){
    if('a' in arg){
        arg.a
    } else if('b' in arg){
        arg.b
    } else { 
        arg.C
    }
}
Micmac answered 11/4, 2019 at 20:54 Comment(2)
I like this solution. But knowing that when we use in operator, it would check the presence of specified property even in the prototype chain, is there a better way that check only non-inherited properties?Musetta
@YadhuKiran The type system does have a way to model own properties vs inherited properties, You can create a custom type guard that only checks for own-properties but it might give wonky results at runtime vs compile time. If you want a deeper explanation ask a separate question, it is difficult to write a long explanation in commentsMicmac
K
5

You can use a type guard:

function isA(arg: A | B | C): arg is A {
    return (<A>arg).a !== undefined;
}

function isB(arg: A | B | C): arg is B {
    return (<B>arg).b !== undefined;
}

function foo(arg: A | B | C) {
    if (isA(arg)) {
        // do stuff for type a
    } else if (isB(arg)) {
        // do stuff for type b
    } else {
        // do stuff for type c
    }
}
Kelsey answered 11/4, 2019 at 20:50 Comment(2)
Custom type guard is overkill in this case IMOMicmac
In my case it will be enaugh to use second answer, but this is really helpful.Steels

© 2022 - 2024 — McMap. All rights reserved.