My goal is to develop a versatile function with a generic string union parameter that defines an object parameter where the property name depends on the generic parameter itself.
Consider this scenario: The variable params
can be either {customerId: 'some string'}
or {supplierId: 'some string'}
. I aim to infer T
from params
, so that when a customerId
is provided, the function is invoked as foo<'customer'>
:
type Type = 'customer' | 'supplier'
type EncodedType<T extends Type> = `${T}Id`;
// This needs to be a mapped type to prevent becoming Record<'customerId' | 'supplierId', string>,
// i.e., with both properties always present
type Params<T extends Type> = {
[P in T]: Record<EncodedType<P>, string>;
}[T];
function foo<T extends Type>(params: Params<T>) {
}
However, when calling this as foo({customerId: '42'})
, T
is inferred as Type
, rather than as customer
.
If I explicitly specify
foo<'customer'>({customerId: '42})
, then the params
is validated to meet Params<'customer'>
. But without specifying T
, it remains as Type
, not inferred from the passed object.
I have found success by utilizing two generic parameters:
type DecodedType<T extends EncodedType<Type>> = T extends `${infer P}Id` ? P : never;
function foo2<K extends EncodedType<Type>, T extends DecodedType<K>>(params: Record<K, string>) {
}
foo2({customerId: '42'}) // called as foo2<'customerId', 'customer'>
... TypeScript can infer K
from the record and subsequently infer T
from
K</code. However, inferring a transformed type from the keys of a record like in the initial example seems challenging.</p>
<p>I prefer to maintain clean functionality with just one generic type. Is it feasible to modify the function to take <T extends Type> and infer <code>T
from the params
object?
Edit: As my previous example was oversimplified, here's another quirky demonstration:
type Type = 'foo' | 'bar';
type Params<T extends Type> = {
foo: {a: number};
bar: {b: number};
}[T];
declare function func<T extends Type>(params: Params<T>): Uppercase<T>;
const result = func({a: 42});
In this case, T
doesn't get inferred into 'foo'
or
'bar'</code from the key names in <code>params</code. Instead, <code>T
stays as 'foo' | 'bar'. Consequently, the resulting output of <code>func
will be 'FOO' | 'BAR'
, instead of merely 'FOO'
.
Although there's technically nothing wrong with the code for foo2
above, it irks me that inferring a type
T</code with the value <code>'customer'
from an object parameter by examining the presence of a key named customerId</code isn't straightforward; only deriving the key name <code>customerId
from the generic parameter customer
appears practical.