I am trying to create a generic TypeScript function that recursively looks through a tree structure to find an item with a specific id value. In order to make it reusable I'm trying to use generics and you just give the function the top level, the id value, and the names of the properties to use for id and children. Here is my example class:
export class Thing{
name: string;
id: string;
children: Thing[];
constructor(){
this.name='';
this.id='';
this.children=[];
}
}
And the method to find the item by id:
export function GetGenericItemById<T>(
allItems: T[],
idField: keyof T,
childrenField: keyof T,
id: string
): any {
const item = allItems.find((x) => x[idField] === id);
if (item != null) {
return item;
}
const subItems = allItems.flatMap((i) => i[childrenField]);
if (subItems.length === 0) {
return undefined;
}
return GetGenericItemById(subItems, idField, childrenField, id);
}
However, on the find function where it makes the comparison x[idField] === id it tells me This condition will always return 'false' since the types 'T[keyof T]' and 'string' have no overlap.. The intent here is to get that property from the object and get it's value. What am I doing wrong here?
I did do this without generics like this:
export function GetGenericItemById(
allItems: any[],
idField: string,
childrenField: string,
id: string
): any {
const item = allItems.find((x) => x[idField] === id);
if (item != null) {
return item;
}
const subItems = allItems.flatMap((i) => i[childrenField]);
if (subItems.length === 0) {
return undefined;
}
return GetGenericItemById(subItems, idField, childrenField, id);
}
This works, however you can also put whatever you want into the idField and childrenField property so you could mess it up pretty easily. It would be nice to restrict those to only be valid keys of the type you are using. I thought my attempt above was doing that but it doesn't seem to be the same since it's giving that error.
EDIT
Per request here is a simple example of how I intent to use it. Say I have a simple data structure that has 'things'. Each Thing has an id and a name as well as potential children that are other Things. Like this:
const data=[
{
name: "thing 1",
id: "1",
children: [
{
name: "thing 1a",
id: "1a",
children: []
}
]
},
{
name: "thing 2",
id: "2",
children: [
{
name: "thing 2a",
id: "2a",
children: []
}
]
}
];
My intent would be to call the function something like this:
const foundThing = GetGenericItemById<Thing>(topThings, 'id', 'children', '1a');
In this example topThings would be a collection that just contained the top level items (ids 1 and 2 in the sample data). Basically it just searches down the tree to find that item. I would expect foundThing to be the Thing object with id '1a'.
EDIT
You also really shouldn't have to put the <Thing> portion in since it will be implied by the first argument but I'm leaving it in there for clarity.