In your request, you mentioned:
let bar:Bar = foo<Bar>(Bar); //Works fine actually
let bars:Bar[] = foo<Bar[]>(Bar); //Should return a Bar[]
This approach will not work because types are erased at runtime. The JavaScript code for both will look like this:
let bar = foo(Bar);
let bars = foo(Bar);
Hence, there's no way to distinguish between them.
A possible solution suggested by @AlekseyL is to have two separate functions to handle the array and non-array cases. However, if you insist on using a single function, we need to find a way to determine what to do at runtime.
First, let's define the Constructor
type to simplify things:
type Constructor<T> = {
new(...args: any[]): T;
}
Next, let's create an ArrayOf
type which holds the class constructor. We also need a type guard to identify if something is an ArrayOf
at runtime, along with a function that developers can utilize to create an ArrayOf
:
type ArrayOf<T> = {
clazz: Constructor<T>;
}
function isArrayOf<T>(x: Constructor<T> | ArrayOf<T>): x is ArrayOf<T> {
return 'clazz' in x;
}
function ArrayOf<T>(clazz: Constructor<T>): ArrayOf<T> {
return { clazz };
}
Finally, let's implement the foo()
function:
function foo<T>(clazz: Constructor<T>): T;
function foo<T>(arrayOfClazz: ArrayOf<T>): T[];
function foo<T>(x: Constructor<T> | ArrayOf<T>): T | T[] {
if (isArrayOf(x)) {
return [new x.clazz()];
} else {
return new x();
}
}
This overloaded function can take either a constructor or an ArrayOf
object and determines the appropriate action at runtime. Let's test it out:
class Bar {
// ...
}
const bar: Bar = foo(Bar);
const bars: Bar[] = foo(ArrayOf(Bar));
It should work as expected!
However, this method may seem complex compared to having two separate functions. Both approaches would require similar effort from developers:
// original request
let bar = foo<Bar>(Bar);
let bars = foo<Bar[]>(Bar);
// alternative solution
let bar = foo(Bar);
let bars = foo(ArrayOf(Bar));
// using two functions
let bar = foo(Bar);
let bars = fooArray(Bar);
The two-function approach might be more user-friendly, but it's ultimately up to personal preference. In any case, I hope this information helps. Good luck!
Update
I noticed you added a new data
parameter to the foo
function, which indicates whether the input is an array or not. Based on this information, here is a straightforward solution:
function foo<T>(data: any[], clazz: Constructor<T>): T[];
function foo<T>(data: any, clazz: Constructor<T>): T;
function foo<T>(data: any, clazz: Constructor<T>): T | T[] {
if (Array.isArray(data)) {
return [new clazz()];
} else {
return new clazz();
}
}
const bar: Bar = foo('bar', Bar);
const bars: Bar[] = foo(['bars'], Bar);
Wishing you success with this updated approach.