I have the following generic function:
function getFoodProperties<T, K extends keyof T>(foods: T[], keys: K[]): any[] { // <-- any needs to be typed according to generic types?
return foods.map((food) => {
let obj: any = {}; // <-- this any also needs typing - it should be the same type as the one above I think?
keys.forEach((key) => {
obj[key] = food[key];
});
return obj;
});
}
const foods: IFoods[] = [
{ name: "Beef", colour: "Red", weight: 8 },
{ name: "Chips", colour: "Yellow", weight: 1 },
{ name: "Lettuce", colour: "Green", weight: 9 },
];
getFoodProperties(foods, ["name", "colour"]);
I have it mostly typed I think, but I'm having trouble typing the return for the new object shape. The object keys are being picked according to the items in the getFoodProperties
second argument. It's so far outputting the correct data, but it's not typed.
It seems without assigning a type to the let obj
, it errors with:
Type 'K' cannot be used to index type '{}'
So I believe I need to create a generic type used for the function return value, and the inner object in the foods
map, but I'm not sure how to approach it.
CodePudding user response:
You almost have it already. Are you aware of the Pick
helper type? The Pick<T, K>
creates a new object type where the keys K
are 'picked' from the type T
.
In your case, the correct return type should be Pick<T, K>[]
, and this seems to give the inference you desire:
function getFoodProperties<T, K extends keyof T>(foods: T[], keys: K[]): Pick<T, K>[] {
// ...
}
// This is now an array of objects with "name" and "colour" properties only.
const result = getFoodProperties(foods, ["name", "colour"]);
result.forEach(food => {
food.name;
food.colour;
food.weight; // Property 'weight' does not exist on type 'Pick<IFoods, "name" | "colour">'
})
For the type of your inner variable obj
, I would recommend just leaving it as any
. You could use the same type, Pick<T, K>
for that, but in that case TypeScript will require both the name and colour properties to be defined in the object right away, you cant assign them as a second step as you do now. If you absolutely want to avoid using any
, then consider using Partial<Pick<T, K>>
with a cast on the return line to change the type back to just Pick<T, K>
:
function getFoodProperties<T, K extends keyof T>(foods: T[], keys: K[]): Pick<T, K>[] {
return foods.map((food) => {
let obj: Partial<Pick<T, K>> = {};
keys.forEach((key) => {
obj[key] = food[key];
});
return obj as Pick<T, K>;
});
}