Home > OS >  Typescript narrow parameter type based on discriminator
Typescript narrow parameter type based on discriminator

Time:12-13

I have a legacy API as shown below (playground link)...

type Command1 = {
    cmd: "my first command",
    arg1: string,
    arg2: boolean
}

type Command2 = {
    cmd: "my second command",
    foo: string,
    bar: number
}

type Command = Command1 | Command2

function execute(cmd: Command["cmd"], args:any /* would like to strongly type this */) {
    console.log(args)
}

execute("my first command", {/* oops missing props */})

Is there any way to type check the args param of the execute function without changing the parameter list of the function?

Thanks

CodePudding user response:

Use Extract<Type, Union> (playground):

function execute<Cmd extends Command["cmd"]>(
  cmd: Cmd, 
  args: Omit<Extract<Command, { cmd: Cmd }>, "cmd">
) {
    console.log(cmd, args)
}

// execute<"my first command">(cmd: "my first command", args: Omit<Command1, "cmd">): void
execute("my first command", { arg1: "ksd", arg2: true })

// execute<"my second command">(cmd: "my second command", args: Omit<Command2, "cmd">): void
execute("my second command", { foo: "qwe", bar: 123 })

CodePudding user response:

Perhaps you can try function overloading:

function execute(cmd: Command1["cmd"], args:Omit<Command1, 'cmd'>) : void;
function execute(cmd: Command2["cmd"], args:Omit<Command2, 'cmd'>) : void;
function execute(cmd: Command["cmd"], args:Omit<Command, 'cmd'>): void {
  console.log(args);
}


execute("my first command", {arg1: '1', arg2: true})
execute("my first command", {foo: '1', bar: 2}) // <-- error

CodePudding user response:

How about using an intersection type with generics for this? I'm assuming the rest of the code is the same as your example:

function execute<T extends Command["cmd"]>(cmd: T, args: Command & { cmd: T }) {
    console.log(cmd, args)
}

I can't think of a better way of "gluing" together the discriminator and the right type of Command.

  • Related