type Foo = {
t: 'foo'
dataFoo: string
}
type Bar = {
t: 'bar',
dataBar: string
}
type Baz = {
t: 'baz'
}
type FooBarBaz = Foo | Bar | Baz
function test<T extends FooBarBaz>(t: T['t'], data: Omit<T, 't'>) {
console.log(data)
}
// Works as intended
test('foo', { dataFoo: 'FOO!' })
// Nooo! Why!?
test('foo', { dataBar: 'BAR!' })
// For the "goal" pt. 3
// Error, but making the `data` parameter completely optional is not the solution,
// as I would be able to call `test('foo')` without passing `data` as well...
test('baz')
So my goal was to be able to call the function like this:
- First parameter is the
t
field of one of those types - The second parameter is the remaining fields of the type with specified
t
- If there are none fields left, then the second parameters is optional
(To-do later, if possible)
CodePudding user response:
The problem is that when you have a generic object type like you have specified, you lose the specific string inferration for generics. This can be fixed by creating another generic for the key then specifying the second generic as the actual object but forced to contain { t: TKey }
. I also had to convert your code to use rest parameters as that's the only way to have conditionally optional parameters. I did this by checking if the keys excluding "t"
is never
which means there are no more keys then require []
otherwise the expected data. Here's the fixed code:
type Foo = {
t: 'foo'
dataFoo: string
}
type Bar = {
t: 'bar',
dataBar: string
}
type Baz = {
t: 'baz'
}
type FooBarBaz = Foo | Bar | Baz
function test<TKey extends string, T extends FooBarBaz & { t: TKey }>(t: TKey, ...args: keyof Omit<T, 't'> extends never ? [] : [data: Omit<T, 't'>]) {
const [data] = args;
console.log(data)
}
// Works as intended
test('foo', { dataFoo: 'FOO!' })
test('bar', { dataBar: 'BAR!' })
test('baz')
// correctly fails!
test('foo', { dataBar: 'BAR!' })
test('bar', { dataFoo: 'FOO!' })