BANO notIT
BANO notIT

Reputation: 360

Selecting element from typescript union by its value

I have union of such shape:

type Message = 
  | { type: 'first', someParam: any } // let it be AMsg
  | { type: 'second', someAnotherParam: any, andOneAnother: any }

I want to get an element of this union as a separate type like so:

type AMsg = MessageOfType<'first'> // = { type: 'first', someParam: any }

I've tried to write my own MessageOfType:

type MessageOfType<T extends Message['type']> = Message['type'] extends T ? Message : never

but it allways returns never.

What should I use to get an element or some elements with type matching needed value from union?

Upvotes: 5

Views: 1878

Answers (4)

BANO notIT
BANO notIT

Reputation: 360

My final solution is this:

type MessageOfType<T extends Message['type']> = Extract<Message, { type: T }>

It's less magical then extends { type: T } and uses standard type.

Upvotes: 0

Maciej Sikora
Maciej Sikora

Reputation: 20132

type MessageOfType<
T extends Message['type'], 
_M extends Message = Message> = _M extends {type: T} ? _M : never   

type First = MessageOfType<'first'>

Playground Link

Explanation:

  • _M extends Message = Message local type variable - its generic in order to force TS to narrow the type. Using Message directly would not narrow it
  • _M extends {type: T} ? _M : never - we say if our _M has wanted type property then we want it, if not we take never. As _M is union then the result will be { type: 'first', someParam: any } | never. never is neutral for | and will be skipped in result we get what we need.

Upvotes: 1

FK82
FK82

Reputation: 5075

Instead of doing type acrobatics you might just want to refactor:

 type Message = AMsg | BMsg
 type AMsg = { type: 'first', someParam: any }
 type BMsg = { type: 'second', someAnotherParam: any, andOneAnother: any }

Playground Link

Upvotes: 2

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

You can just use the predefined conditional type Extract for this:

type Message = 
  | { type: 'first', someParam: any } // let it be AMsg
  | { type: 'second', someAnotherParam: any, andOneAnother: any }

type AMsg = Extract<Message, { type: 'first' }>

Playground Link

Upvotes: 7

Related Questions