interface IData {
title: string;
slug: string;
published_at: string;
isPopular: boolean;
}
const product: IData = {
title: "Title 1",
slug: "title-1",
published_at: "2012",
isPopular: true,
};
function getCourseBySlug(fields: Array<keyof IData>) {
const items = <IData>{};
// // Ensure only the minimal needed data is exposed
fields.forEach((field) => {
if (field === "isPopular") {
items[field] = product.isPopular;
}
if (typeof product[field] !== "undefined") {
items[field] = product[field]; // I am getting error here items[field]
}
});
return items;
}
I am getting the error at "items[field]". I already tried a few solutions to other similar questions. But nothing worked for me. What I am missing here? Error message: Type 'string | boolean' is not assignable to type 'never'. Type 'string' is not assignable to type 'never'
CodePudding user response:
The issue is because items is an empty object so items[field] will "never" exist
switching to a reduce method will both solve the error (since you are not trying to access a value that doesn't exist) and cleans up your function slightly
function getCourseBySlug(fields: Array<keyof IData>): IData {
return fields.reduce((acc: IData, field: keyof IData) => {
if (typeof product[field] !== "undefined") {
return { ...acc, [field]: product[field] };
} else {
return acc;
}
}, {
title: "",
slug: "",
published_at: "",
isPopular: false,
});
}
--edit--
if you want your returned object to be a subset of the original as pointed out in comments then you can replace the return and accumulator types with Partial
function getCourseBySlug(fields: Array<keyof IData>): Partial<IData> {
return fields.reduce((acc: Partial<IData>, field: keyof IData) => {
if (typeof product[field] !== "undefined") {
return { ...acc, [field]: product[field] };
}
}, {});
}
CodePudding user response:
There are two distinct issues:
Your
items
is not a validIData
object. It doesn't have all of the requiredIData
properties.In your loop, TypeScript doesn't know the specific type of
field
(just that it's one ofkeyof IData
) and so doesn't know whetheritems[field]
isstring
orboolean
; as a result, you can't assign to it. Even though you know thatitems[field]
andproduct[field]
will have the same type, TypeScript doesn't.
The first issue is simple: Define it as a partial IData
:
const items: Partial<IData> = {};
You have at least three options for fixing #2:
Make the function fully typesafe with a helper.
function copyProp<ObjType extends object, KeyType extends keyof ObjType>(obj: ObjType, key: KeyType, value: ObjType[KeyType]) { obj[key] = value; } function getCourseBySlug(fields: Array<keyof IData>) { const items: Partial<IData> = {}; for (const field of fields) { copyProp(items, field, product[field]); } return items; }
That works because
copyProp
ensures that the value passed in (product[field]
) is type-compatible with the target object's field viaObjType[KeyType]
.Tell TypeScript to ignore the error. This is rarely the right thing to do, but sometimes in cases where you know the error isn't important, it's not unreasonable.
function getCourseBySlug(fields: Array<keyof IData>) { const items: Partial<IData> = {}; for (const field of fields) { // @ts-ignore items[field] = product[field]; } return items; }
You could do an end-run around it by hiding the issue behind
Object.fromEntries
:function getCourseBySlug(fields: Array<keyof IData>) { const items: Partial<IData> = Object.fromEntries(fields.map(field => [field, product[field]])); return items; }
The first of those is fully typesafe (I think). The second two are both imperfect, but in a very contained way. getCourseBySlug
is a small function and within it you know that items[field]
and product[field]
have the same type.
Note that in all of those, I removed your typeof product[field] !== "undefined"
test. There's no reason for it, the type of fields
ensures that field
is a valid key for product
, and none of IData
's properties is optional.