Home > database >  Typescript merge n arrays of objects
Typescript merge n arrays of objects

Time:10-29

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.

Playground

  • Related