I'd be inclined to give this an overloaded type signature corresponding to each case. There are a few snags here: one is that you're checking just the first element of responses
and assuming the rest of the elements are of the same type; but arrays can be heterogeneous. And since arrays in JS and TS are considered objects, the call signature for combineResults()
might do weird things if you give it a heterogenous array like [[1, 2, 3], {a: 1}]
. I don't know what you want to happen at runtime there, so I don't know what you want to see happen in the type signature. These are edge cases.
Another snag is that an array like [{a: 1}, {b: ""}]
is considered in TypeScript to be of type Array<{a: number, b?: undefined} | {b: string, a?: undefined}>
, and to turn that into {a: number, b: string}
involves a lot of type-system hoop-jumping, including turning unions to intersections and filtering out undefined
properties.
So, here goes:
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type Defined<T> = T extends any ? Pick<T, { [K in keyof T]-?: T[K] extends undefined ? never : K }[keyof T]> : never;
type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
function combineResults<T extends ReadonlyArray<any>>(responses: ReadonlyArray<T>): T[number][];
function combineResults<T extends object>(responses: ReadonlyArray<T>): Expand<UnionToIntersection<Defined<T>>>;
function combineResults<T extends ReadonlyArray<any>>(responses: T): T;
function combineResults(responses: readonly any[]) {
if (responses.length === 0) {
return [];
}
if (Array.isArray(responses[0])) {
return responses.flat(1);
} else if (typeof responses[0] === 'object') {
return Object.assign({}, ...responses);
}
else {
return responses;
}
}
The call signature should map arrays-of-arrays to an array, arrays-of-objects to an object, and any other array to itself. Let's test it:
const arrs = combineResults([[1, 2, 3], ["a", "b"]]); // (string | number)[]
const objs = combineResults([{ a: 1 }, { b: "hey" }]) // {a: number, b: string}
const nons = combineResults([1, 2, 3]); // number[]
Looks good, I think. Watch out for edge cases, though:
const hmm = combineResults([[1, 2, 3], { a: "" }])
/* const hmm: {
[x: number]: number;
a: string;
} ?!?!? */
You might want to tune those signatures to prevent heterogeneous arrays entirely. But that's another rabbit-hole I don't have time to go down right now.
Okay, hope that helps; good luck!
Playground link to code