简体   繁体   中英

Typescript: Infer generic type by checking string value

I have types like this:

export type NotificationType = 'comment' | 'like' | 'message' | 'follow' | unknown

type MessageBody<T extends NotificationType> = T extends 'comment'
  ? {
      body: string
      threadId: string
      projectId: string
    }
  : T extends 'like'
  ? {
      resourceId: string
      resourceType: 'project' | 'projectVersion' | 'thread' | 'comment'
    }
  : T extends 'message'
  ? {
      body: string
      chatId: string
    }
  : T extends 'follow'
  ? {}
  : never

export type Message<T extends NotificationType = unknown> = {
  channel: string
  message: MessageBody<T> & {
    type: T
  }
  timetoken: string | number
  uuid?: string
  publisher?: string
}

What I want to do now is infer the type of T by checking the property message.type without having to specify the type of T beforehand, so something like this:

function checkMessage(message: Message) { // No generic type given
  switch (message.message.type) {
    case 'comment':
      message.message.body // infers to MessageBody<'comment'>
  }
}

But with this approach I get an error on the switch statement with: Property 'type' does not exist on type 'never'.

Which probably comes from the usage of unknown inside the NotificationType . How can I have the generic optional and still infer the type by checking the string?

I'd willing to bet that NotificationType should only be a string. Hence, using unknown is not the best idea for representing unknown string type. It is much better to use just refular string type. However, using string type with other literal strings like comment and follow will clash them to just a string . This is why t is better to use string & {} .

Also, I have used map data stucture instead conditional type MessageBody .

export type KnownType = 'comment' | 'like' | 'message' | 'follow'

export type UnknownType = string & {}

type NotificationType = KnownType | UnknownType

type MessageMap = {
  comment: {
    body: string
    threadId: string
    projectId: string
  },
  like: {
    resourceId: string
    resourceType: 'project' | 'projectVersion' | 'thread' | 'comment'
  },
  message: {
    body: string
    chatId: string
  },
  follow: {}
}

type MessageBase = {
  channel: string
  timetoken: string | number
  uuid?: string
  publisher?: string
}

type MessageBody<T extends KnownType> = { message: MessageMap[T] & { type: T } } & MessageBase

export type ComputeMessage<T extends NotificationType> = T extends KnownType ? MessageBody<T> : never

function checkMessage<MessageType extends NotificationType>(message: ComputeMessage<MessageType>) {

  switch (message.message.type) {
    case 'comment':
      message.message.projectId // ok
      message.message.threadId // ok
  }
}

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM