admin管理员组文章数量:1123427
I have the following function that transforms array elements into objects:
function mapToCreateObjects<K extends keyof U, T, U extends Record<K, T[]>>(
obj: U,
key: K,
): Record<K, { create: T }[]> {
return {
[key]: obj[key].map((item) => ({ create: item })),
} as Record<K, { create: T }[]>;
}
const data = {
fruits: ['apple', 'banana', 'cherry'],
vegetables: [1, 2, 3],
};
const transformedFruits = {
...mapToCreateObjects(data, 'fruits'),
...mapToCreateObjects(data, 'vegetables'),
};
I expect transformedFruits
to have this type:
{ fruits: { create: string }[], vegetables: { create: number }[] }
But TypeScript infers { create: unknown }[]
for both. Why is TypeScript inferring unknown
and how can I fix it to infer the correct types for create
?
I have the following function that transforms array elements into objects:
function mapToCreateObjects<K extends keyof U, T, U extends Record<K, T[]>>(
obj: U,
key: K,
): Record<K, { create: T }[]> {
return {
[key]: obj[key].map((item) => ({ create: item })),
} as Record<K, { create: T }[]>;
}
const data = {
fruits: ['apple', 'banana', 'cherry'],
vegetables: [1, 2, 3],
};
const transformedFruits = {
...mapToCreateObjects(data, 'fruits'),
...mapToCreateObjects(data, 'vegetables'),
};
I expect transformedFruits
to have this type:
{ fruits: { create: string }[], vegetables: { create: number }[] }
But TypeScript infers { create: unknown }[]
for both. Why is TypeScript inferring unknown
and how can I fix it to infer the correct types for create
?
2 Answers
Reset to default 2I defer to @Mark Wiemer's comment about Typescript inferring unknown
. I found that I could get it to return the correct types by using typeof obj[K][number]
in the return type.
function mapToCreateObjects<K extends keyof U, T, U extends Record<K, T[]>>(
obj: U,
key: K,
): Record<K, { create: typeof obj[K][number] }[]> {
return {
[key]: obj[key].map((item) => ({ create: item })),
} as Record<K, { create: typeof obj[K][number] }[]>;
}
The transformedFruits
variable now has this type:
{ vegetables: { create: number }[], fruits: { create: string }[] }
I hope this is helpful!
TypeScript does not use generic constraints as inference sites for other generic type parameters. That is, U extends Record<K, T[]>
does not help TypeScript infer T
. Given the call signature
<K extends keyof U, T, U extends Record<K, T[]>>(
obj: U, key: K
) => Record<K, { create: T }[]>
if you call that function like f(obj, key)
, TypeScript can infer U
from the type of obj
, and K
from the type of key
. But T
cannot be inferred from anywhere at all. There's no argument whose type is directly related to T
. Since T
has no inference site, inference just fails, and it falls back to its own constraint (and since T
has no explicit constraint, it uses the implicit constraint) which is unknown
.
All U extends Record<K, T[]>
accomplishes is to check U
against Record<typeof key, unknown[]>
after it's too late.
There is an old feature request at microsoft/TypeScript#7234 to treat constraints as inference sites, but it was eventually closed, as you can almost always rewrite your generics in a way the TypeScript can infer, often with intersections (e.g., rewrite <T, U extends F<T>>(u: U) => void
as <T, U extends F<T>>(u: U & F<T>) => void
).
In your example, it looks like T
really isn't needed as its own generic type parameter. You have all the information you need from K
and U
. That is, since U
is a Record<K, T[]>
, then U[K]
is assignable to T[]
, so U[K][number]
is assignable to T
. So let's just forget about T
and use U[K][number]
instead:
function mapToCreateObjects<
K extends keyof U,
U extends Record<K, any[]>
>(obj: U, key: K,): Record<K, { create: U[K][number] }[]> {
return {
[key]: obj[key].map((item) => ({ create: item })),
} as any
}
Here I had to write something in place of T
inside the constraint for U
, so I just used the any
type. There are usually ways to write code and completely avoid any
, so feel free to do that if it matters to you.
Anyway we can now verify that your code behaves as desired by inspecting the inferred type of `transformedFruits:
/* const transformedFruits: {
vegetables: {
create: number;
}[];
fruits: {
create: string;
}[];
} */
Looks good.
Playground link to code
版权声明:本文标题:TypeScript inferring incorrect return type when transforming array elements into objects - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736569905a1944756.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
unknown
on very complex types. It's frustrating for sure. I don't have an answer, but I can say as a TS dev of 4+ years that your code looks good to me, and I'd just cast the results as needed. Make sure you're using a recent version of TS, it may have improvements here :) – Mark Wiemer Commented 16 hours agoT
, because constraints are not used as inference sites, see stackoverflow.com/q/76294709/2887218. You should dropT
, only inferU
andK
and then computeT
from it (which isU[K][number]
), as shown in this playground link. Does that fully address the question? If so I'll write an answer explaining. If not, what's missing? – jcalz Commented 14 hours ago