Home > Mobile >  How to implement "If not interface, add necessary properties to match"?
How to implement "If not interface, add necessary properties to match"?

Time:03-31

I struggled with the title, but the code example is very straightforward:

// Setup
interface Dog {
    name: string;
}

interface Dachshund extends Dog {
    length: number;
}

function isDachshund (dog: Dog): dog is Dachshund {
    return (dog as Dachshund).length !== undefined;
}

// Problem
// We have an object that could match two different interfaces
let unknownDog: Dog | Dachshund = { name: "Rufus" };

// If it's not this specific interface, add the missing property
if (!isDachshund(unknownDog)) {
    (unknownDog as Dachshund).length = 5;
}

unknownDog // compiler still thinks Dog | Dachshund, but we know for certain it's a Dachshund at this point

Is it possible to get the TypeScript compiler to infer that we've added the necessary properties to narrow down the union type? Is there a different way of structuring my types to avoid this kind of problem?

CodePudding user response:

Interesting question.

As far as I can see, (and by all means someone correct me if I'm wrong) it looks like TypeScript simply won't support what you're specifically trying to do here.

See this github issue: https://github.com/microsoft/TypeScript/issues/27568

Essentially what you're trying to do is get TypeScript to be aware of mutations you've made to an object of a wider type, and infer correctly the narrower type.

The above Github issue seems to suggest 'Nope, too hard'.

Note that TypeScript won't keep track of mutations of the sort when using no type annotations!

const rufus = {
    name: "foo"
}; 


rufus.length = 9; //Property 'length' does not exist on type '{ name: string; }'.(2339)

What I would do here is just not mutate the object, instead just declare new objects:


function usesADog(unknownDog: Dog | Dachshund){

    const dogToUse = isDachshund(unknownDog) ? unknownDog: {...unknownDog, length: 5}; 

    dogToUse; //const dogToUse: Dachshund

}

Playground

CodePudding user response:

Typescript knows that the type of unknownDog is Dachshund, only in any block guarded by a call to isDachshund function. Because the function return type is dog is Dachshund(type predicate).

if (!isDachshund(unknownDog)) {
    ... //here the type of unknownDog is Dog.  
}

else {
   ... // and here the type is Dachshund
}

// but here again the type is Dog|Dachshund

So, in if block you can write the codes you want to be executed, if the type of unknownDog is Dog.
In else block the codes you want to be executed, if the type of unknownDog is Dachshund.

  • Related