Reputation: 182
I'm working on a feature that retrieves a list of products from some E-commerce API. I'm trying to add an ability to request specific fields from the products, removing the unnecessary fields.
This is the code:
interface Product {
images: string[],
title: string;
id: number;
currency: string;
price: number;
created: string | Date;
description: string;
}
const getProducts = (selectedProperties: (keyof Product)[]) => {
// imagine this is a call to an API to get a list of products
const products: Product[] = [
{
id: 1,
images: [],
title: 'a shirt',
currency: "USD",
price: 10,
created: "2021-04-29T11:21:53.386Z",
description: "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."
}
];
if(selectedProperties?.length){
return products.map(product => {
const result: Partial<Product> = {};
selectedProperties.forEach(prop => {
result[prop] = product[prop]; // <--- This line returns the error: Type 'string | number | string[] | Date' is not assignable to type 'undefined'. Type 'string' is not assignable to type 'undefined'
})
return result;
})
}
return products;
}
Here is a link to a code sandbox with the error so you could see it for yourself: link (look at line 30)
What am I doing wrong here exactly that is causing the TypeScript error?
Upvotes: 6
Views: 2038
Reputation: 329943
The problem here is that the compiler infers the type of prop
to be keyof Product
, which is a wide type corresponding to multiple possible strings. And while you understand that result[prop] = product[prop]
should be fine because both refer to the same exact value named prop
, the compiler only really sees the types of these things. It can't see the difference between that and result[prop2] = product[prop1]
where prop2
and prop1
are both keyof T
. You'd agree that such a line is a mistake unless you can constrain prop1
and prop2
to the very same literal key type.
This is a pain point in TypeScript; there is some discussion in microsoft/TypeScript#30769, the change made for TypeScript 3.5 responsible for this checking... it improved soundness, but at the expense of adding some false positives like this. The specific problem with copying properties is an open issue at microsoft/TypeScript#32693. This comment implies that the TS team is aware of the issue and think something should be done to support copying properties from one object to another. But who knows when or if this will actually happen. If you care you might want to go there and give it a 👍, but I doubt that will have much of an impact.
For now the way to proceed is probably to make the callback generic in K extends keyof Product
and have prop
be of type K
:
selectedProperties.forEach(<K extends keyof Product>(prop: K) => {
result[prop] = product[prop]; // no error
})
This makes the error go away. Technically this has the same problem as before, since nothing stops K
from being the full union keyof Product
, but the compiler explicitly allows assignments from Product[K]
to Product[K]
for generic K
, despite such potential unsoundness.
Upvotes: 4