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 ofinterface
types; see ms/TS#15300.note that your input type
[...{ [i in keyof T] : T[i] }]
is indistinguishable fromT
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.
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"
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
.