Home > Mobile >  Infering type of object from value of property in Typescript
Infering type of object from value of property in Typescript

Time:06-13

I have the following code in Typescript (simplified)

interface TimeoutOption {
  category: 'timeout'
  time: number
}

interface UserOption {
  category: Parameters<typeof addEventListener>[0]
}

type MyDelayOptions = Array<TimeoutOption | UserOption>


function delay(options: MyDelayOptions) {
  for (const option of options) {
    if (option.category === 'timeout') {
      timeout = setTimeout(() => {
        // not relevant
      }, option.time) // need to cast and do (option as TimeoutOption) to not get an error
    }
  }
}


In order to avoid a compilation error I have to add the type assertion mentioned in the comment. For a human though it is clear that if the category is 'timeout' my option is of type TimeoutOption. How can I make this work without the type assertion? Complete refactors welcome.

CodePudding user response:

The problem is that UserOption defines category as type string (indirectly, but that's what it comes out as). As a result, if (option.category === 'timeout') doesn't discriminate between your two union members, because UserOption could easily have category: "timeout" just like TimeoutOption does. The discriminant (category) has to clearly discriminate the union, but it doesn't in your case.

You could test for the presence of time instead (see *** below):

function delay(options: MyDelayOptions) {
  for (const option of options) {
    if ("time" in option) { // ***
      const timeout = setTimeout(() => {
        // not relevant
      }, option.time) // need to cast and do (option as TimeoutOption) to not get an error
    }
  }
}

Playground link

CodePudding user response:

Looks like you need a typeguard function here to confirm that option is of type TimeoutOption.

https://www.typescriptlang.org/docs/handbook/advanced-types.html

Something like this should do it.

function isTimeout(option: TimeoutOption | UserOption): option is TimeoutOption {
  return (option as TimeoutOption).time !== undefined;
}

...

if (itTimeout(option) {
 // do stuff
}

Playground link

  • Related