I am getting the typescript error in angular application while trying to set types for a data inside reduce method.
error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ICar'.
No index signature with a parameter of type 'string' was found on type 'ICar'.
Back-end side sends me following array of objects which I pass to getCarProperty function:
[
{
created: null
id: "124545484822"
details: {
name: "Ford focus"
number: "128888"
}
status: "working"
usedFrom: null
usedUntil: null
},
{
created: null
id: "99525484827"
details: {
name: "Honda Accord"
number: "598402"
}
status: "working"
usedFrom: null
usedUntil: null
}
]
Here is how function looks like:
getCarProperty = (obj: ICar, path: string): any => {
if (path.indexOf('.') > -1) {
return path.split('.').reduce((o: ICar, p: string) => {
return o && o[p]; <----- HERE IS THE ISSUE
}, obj);
}
return Array.from(path).reduce((o: ICar, p: string) => {
return o && o[p]; <------ Here is the issue
}, obj);
};
When details.name property passes via path argument then I am able to get the value of the name property (such as 'Ford focus' and 'Honda Accord').
Here are interfaces that used for this data:
export interface IDetail {
name: string;
number: string;
}
export interface IContract {
created: string;
id: string;
detail: IDetail;
status: string;
usedFrom: string;
usedUntil: string;
}
I tried to apply different solutions such as [propName as keyof ICar] or set generic types, but none of them were successful. So, what is wrong here?
Any help is appreciated.
CodePudding user response:
The problem is that your reduce callback is expecting an object that comply with the ICar interface which is true only in the first iteration, on the next iteration of the reduce callback, you won't receive anymore an object of ICar type (only IDetails or string), so the constraint fails. Basically, the return of every iteration return a different type and that's why you cannot use a specific type there, you could use a union of types in some cases though.
To fix your issue, just replace the ICar type to any, like below. I don't see any issue with this because you are still type checking the getCarProperty function parameters.
getCarProperty = (obj: ICar, path: string): any => {
if (path.indexOf('.') > -1) {
return path.split('.').reduce((o: any, p: string) => {
return o && o[p];
}, obj);
}
return Array.of(path).reduce((o: any, p: string) => {
return o && o[p];
}, obj);
};
If you really want to preserve the types, and you don't want to use any, you could try the function below, but I don't see much advantage since you are already typing your function parameters.
function getCarProperty(obj: ICar, path: string): any {
type CarOrDetailType = ICar | IDetail; // <-- All the possibilities for the reduce `previousValue` callback parameter. If the ICar interface changes, you might need to adjust this.
type CarOrDetailKey = keyof CarOrDetailType;
if (path.indexOf('.') > -1) {
const typedPath = path.split('.') as Array<CarOrDetailKey>;
return typedPath.reduce((o: CarOrDetailType, p: CarOrDetailKey): any => {
return o && o[p];
}, obj);
}
const typedPath = Array.of(path) as Array<CarOrDetailKey>;
return typedPath.reduce((o: CarOrDetailType, p: CarOrDetailKey) => {
return o && o[p];
}, obj);
}
Two more issues that I found with your code, but it could have been that you miscopied, are:
- Your mock data don't have comas after every property, so it has not a valid syntax.
- You used
Array.from(path)on a string, but you probably wantedArray.of(path).
