0

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

2
  • 2
    Something like this? Commented Oct 28, 2022 at 4:05
  • @caTS Yes! That's exactly what I need. Can you write up an answer and explain your solution? Thank you so much Commented Oct 28, 2022 at 11:20

1 Answer 1

3

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.