Home > Software engineering >  Merge value types of tuple of objects in TypeScript
Merge value types of tuple of objects in TypeScript

Time:07-20

I have a set of objects, each with its own properties:

const a = { a1 : 1, a2 : 2 } as const
const b = { b1 : `1`, b2 : `2` } as const

The function f takes all these objects as a typed tuple:

function f<
    T extends { [key : string] : any }[]
> (
    ...t : [...{ [i in keyof T] : T[i] }]
) {
    // todo
}

f(a, b)

The goal is to return any property of any of these objects. In this case, the expected result should be 1 | 2 | "1" | "2".

The problem is that I can't figure out how to properly describe the return type.

I have tried T[number][keyof T[number]] but it have failed, probably due to possible differences in indexes for T and keyof T.

Then I wrote a wrapper for it:

type PropertyOf<T extends { [key : string] : any }> = T[keyof T]

And specify the return type of f as PropertyOf<T[number]>. But it still doesn't work.

Despite PropertyOf returns expected 1 | 2 for PropertyOf<{ a1 : 1, a2 : 2 }>, when used as PropertyOf<T[number]> in f the function return type is never.

What is the reason and how to fix this? Thanks.

CodePudding user response:

You could use this approach:

declare function f<T extends object[]>(
    ...t: T
): { [I in keyof T]: T[I][keyof T[I]] }[number]
  • note that object is often more forgiving for callers than { [key : string] : any } because the latter's index signature prevents you from passing in values of interface types; see ms/TS#15300.

  • note that your input type [...{ [i in keyof T] : T[i] }] is indistinguishable from T for a rest parameter.

But importantly, we are mapping over the T tuple type and computing your PropertyOf<T> type (also called ValueOf<T> according to Is there a `valueof` similar to `keyof` in TypeScript? ) for each tuple element, then indexing into the resulting tuple with number, resulting in a union of all the types in that tuple.

Let's test it out:

const a = { a1: 1, a2: 2 } as const
const b = { b1: `1`, b2: `2` } as const

const ret = f(a, b);
// const ret: 1 | 2 | "1" | "2"

Looks good.

Playground link to code

CodePudding user response:

We can use the following as the return type for f.

function f<
    T extends { [key : string] : any }[]
> (...t : [...{ [i in keyof T] : T[i] }])
  : T[number] extends Record<string, infer U> ? U : never 
{
   return {} as any
}

const result = f(a, b)
//    ^? const result: 1 | 2 | "1" | "2"

Playground


The keyof operator does not work here because it only returns shared properties when used on object unions. Both objects don't share any properties, so keyof evaluates to never.

  • Related