How to do dynamic typescript return type based on an argument is undefined or not. Please help.
Also added a Typescript Playground Snippet for reference
I have come up with this, but is not working as expected.
V extends keyof T ? ToObject<T> : ToObject<T[keyof T]>
The code:
export interface ToObject<T> {
[k: string]: T;
}
export const toObject = <T, V = (keyof T)>(
list: T[],
key: keyof T,
valueKey?: V
): V extends keyof T ? ToObject<T> : ToObject<T[keyof T]> => valueKey === undefined
? list.reduce((all, cur) => ({
...all,
[cur[key] as unknown as string]: cur
}), {} as ToObject<T>)
: list.reduce((all, cur) => ({
...all,
[cur[key] as unknown as string]: cur[valueKey as unknown as keyof T]
}), {} as ToObject<T[keyof T]>);
Usage :
interface Data{
a: string;
b: string;
c: string;
}
const list: Data[] = [
{a:'a', b: 'b', c: 'c'},
{a:'1', b: '2', c: '3'}
];
// use 1
toObject<Data>(list, 'a');
// response
{
{'a': {a:'a', b: 'b', c:'c'}},
{'1': {a:'1', b:'2', c: '2'}},
}
// Use 2
toObject<Data>(list, 'a', 'b');
//response
{ {'a': 'b'}, {'1': '2'} }
ISSUE:
// use 1 ERROR -> Cant do this
const dataMap: ToObject<Data> = toObject<Data>(list, 'a');
// Type 'ToObject<Data> | ToObject<string>' is not assignable to type 'ToObject<Data>'.
// Type 'ToObject<string>' is not assignable to type 'ToObject<Data>'.
// Type 'string' is not assignable to type 'Data'.(2322)
// Use 2 ERROR -> Cant do this
const dataKeyMap: ToObject<string> = toObject<Data>(list, 'a', 'b');
// Type 'ToObject<Data> | ToObject<string>' is not assignable to type 'ToObject<string>'.
// Type 'ToObject<Data>' is not assignable to type 'ToObject<string>'.
// Type 'Data' is not assignable to type 'string'.(2322)
CodePudding user response:
As written, TS does not have enough information differentiate between toObject
's two return types.
If we add function overloads, we can tie a return type to a function signature. If valueKey
is passed,
toObject
returns ToObject<T[keyof T]>
, if not, then the function returns ToObject<T>
.
export function toObject <T, V = (keyof T)>(list: T[], key: keyof T): ToObject<T>
export function toObject <T, V = (keyof T)>(list: T[], key: keyof T, valueKey: V): ToObject<T[keyof T]>
export function toObject <T, V = (keyof T)>(list: T[], key: keyof T, valueKey?: V): ToObject<T> | ToObject<T[keyof T]> {
return valueKey === undefined
? list.reduce((all, cur) => ({
...all,
[cur[key] as unknown as string]: cur
}), {} as ToObject<T>)
: list.reduce((all, cur) => ({
...all,
[cur[key] as unknown as string]: cur[valueKey as unknown as keyof T]
}), {} as ToObject<T[keyof T]>);
}
In addition to making the TS compiler happy, the overloads also inform human users how they should call the function.