I have a type
named IndexedObject
, which is defined as:
type IndexedObject<T extends {} = { [k: string]: any }> = T & { _i: number }
// It just appends an index "_i" to the object
// The "_i" index is assigned in the first function of the chain,
// and then it doesn't get changed anywhere else
The reason I use this is that I have an input array, and several functions that transform and filter it, and I need to keep track of the original index of each element (it's probably not important in the main question, but I explained it just in case)
So I have an arbitrary number of arrays of type IndexedObject<T1>[]
, IndexedObject<T2>[]
, etc., with T1
, T2
, etc. being different object types with arbitrary keys. And I want to merge them into one array of merged objects. for example:
interface T1 {
id: number
name: string
}
interface T2 {
status: boolean
message: string
}
let arr1: IndexedObject<T1>[] = [
{_i: 0, id: 201, name: 'a'},
{_i: 3, id: 5, name: 'h'}
]
let arr2: IndexedObject<T2>[] = [
{_i: 0, status: true, message: '1234'},
{_i: 1, status: false, message: '5678'},
{_i: 4, status: false, message: '9065'}
]
// merged:
let merged = [
{_i: 0, id: 201, name: 'a', status: true, message: '1234'},
{_i: 1, status: false, message: '5678'},
{_i: 3, id: 5, name: 'h'},
{_i: 4, status: false, message: '9065'}
]
The merging itself is not an issue. This is the function I wrote:
function mergeIndexedArrays(...arrays: IndexedObject[][]) {
return arrays.reduce((result, array) => {
for (let a of array) {
result[a._i] = { ...result[a._i], ...a }
}
return result
}, [])
}
The problem is with typing. I need the return type of the merge function to dynamically contain the keys of all the input types (here T1
and T2
). How can I do this?
Note that there are an arbitrary number of arrays.
I tried to achieve this using variadic tuple types
but I couldn't figure it out.
Thank you
CodePudding user response:
Essentially, we want to "extract" all the types in the indexed objects (infer
), then intersect them together (and also make the result a Partial, since they aren't guaranteed to exist).
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never;
type InferTypes<A> = A extends IndexedObject<infer U>[][] ? U : never;
function mergeIndexedArrays<Arrays extends IndexedObject[][]>(...arrays: Arrays): Partial<UnionToIntersection<InferTypes<Arrays>>>[];
function mergeIndexedArrays(...arrays: IndexedObject[][]) {
return arrays.reduce((result, array) => {
for (let a of array) {
result[a._i] = { ...result[a._i], ...a }
}
return result
}, [])
}
Then we can use rest parameters to collect all the arrays into the generic type Arrays
. Also, I have moved the signature into an overload so it doesn't cause annoying type errors in the function body.