sam256
sam256

Reputation: 1421

How to use discriminated union with enum as discriminant

I have a discriminated union where the discriminator is an enum and the value of the enum field determines whether or not another field is undefined. As so:

enum Status {
  TOP, BOTTOM, MIDDLE
}
type StatusReport = 
| {
  status: Status.TOP | Status.BOTTOM
  message: undefined
} 
| {
  status: Status.MIDDLE
  message: string
}

All fine. Now I introduce a function to build such a type from the appropriate parameters:

function statusReporter(theStatus: Status, statusMessage:string):StatusReport{
  return {
    status: theStatus,
    message: theStatus === Status.MIDDLE ? statusMessage : undefined
  }
}

This function logic looks sound to me. It will assign the correct value to message depending on the value of the enum value being set in status. But TS errors claiming Status is not assignable to type Status.MIDDLE. That's true, of course, but I believe my conditional is ensuring that won't happen.

Why am I getting this error and what's the fix?

Here's the playground

I've tried asserting the type of theStatus as a conditional type dependent on whether or not theStatus extends Status.MIDDLE (status: theStatus as typeof theStatus extends Status.MIDDLE ? Status.MIDDLE : Status), but it doesn't help. I get the same error.

Upvotes: 2

Views: 2582

Answers (2)

ferikeem
ferikeem

Reputation: 537

You have to determine the type before you create your object.

function statusReporter(theStatus: Status, statusMessage:string):StatusReport{
  return theStatus === Status.MIDDLE ? {
    status: theStatus,
    message: statusMessage
  } : {
    status: theStatus,
    message: undefined
  }
}

Playground link

Upvotes: 1

You need to use function overloading:

enum Status {
  TOP, BOTTOM, MIDDLE
}

type WithoutMessage = {
  status: Status.TOP | Status.BOTTOM
  message: undefined
}
type WithMessage = {
  status: Status.MIDDLE
  message: string
}

type StatusReport = WithMessage | WithoutMessage


function statusReporter(theStatus: Status.MIDDLE, statusMessage: string): WithMessage
function statusReporter(theStatus: Exclude<Status, Status.MIDDLE>, statusMessage: string): WithoutMessage
function statusReporter(theStatus: Status, statusMessage: string): StatusReport
function statusReporter(theStatus: Status, statusMessage: string) {
  return {
    status: theStatus,
    message: theStatus === Status.MIDDLE ? statusMessage : undefined
  }
}

function builder(myStatus: Status) {
  return statusReporter(myStatus, "hello");
};
const hof = builder(Status.MIDDLE) // StatusReport

const result = statusReporter(Status.MIDDLE, 'hello') // WithMessage
const result2 = statusReporter(Status.BOTTOM, 'hello') // WithoutMessage
const result3 = statusReporter(Status.TOP, 'hello') // WithoutMessage

Playground

You are getting this error because message property evaluates to string | undefined while StatusReport expects either undefined or message but not both, I mean not union of them

UPDATE

function statusReporter(theStatus: Status, statusMessage: string): StatusReport {
  if (theStatus === Status.MIDDLE) {
    return {
      status: theStatus,
      message: statusMessage
    }
  }
  return {
    status: theStatus,
    message: undefined
  }
}

Upvotes: 1

Related Questions