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?
CodePudding user response:
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
}
}