Search code examples
typescriptcasting

Inferring type of object from value of property in Typescript


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.


Solution

  • 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