Reputation: 29
I want to format plain query to hierarchy json to display on front-end but I don't know how
I may have relational database that has Table called "Todo" which has following column
for example
[
{id: 2, Name:"Task 1.1", ParentID: 1},
{id: 5, Name:"Task 2", ParentID: null},
{id: 6, Name:"Task 2.1", ParentID: 5},
{id: 1, Name:"Task 1", ParentID: null},
{id: 3, Name:"Task 1.2", ParentID: 1},
{id: 4, Name:"Task 1.3", ParentID: 1},
{id: 7, Name:"Task 2.2", ParentID: 5},
{id: 8, Name:"Task 2.3", ParentID: 5},
{id: 9, Name:"Task 3", ParentID: null}
]
to
[
{
"id": 5,
"Name": "Task 2",
"ParentID": null,
"subtasks": [
{
"id": 6,
"Name": "Task 2.1",
"ParentID": 5,
"subtasks": []
},
{
"id": 7,
"Name": "Task 2.2",
"ParentID": 5,
"subtasks": []
},
{
"id": 8,
"Name": "Task 2.3",
"ParentID": 5,
"subtasks": []
}
]
},
{
"id": 1,
"Name": "Task 1",
"ParentID": null,
"subtasks": [
{
"id": 2,
"Name": "Task 1.1",
"ParentID": 1,
"subtasks": []
},
{
"id": 3,
"Name": "Task 1.2",
"ParentID": 1,
"subtasks": []
},
{
"id": 4,
"Name": "Task 1.3",
"ParentID": 1,
"subtasks": []
}
]
},
{
"id": 9,
"Name": "Task 3",
"ParentID": null,
"subtasks": []
}
]
For two level (root and children) my attempt is to push Todo that has no parent ID (root) into an array and then push Todos that have parent into the parent todo
I have tried 2 levels hierarchy and I think it works fine but don't know how to implement more level in my next.js project and I'm not quite sure this is good practice or not.
here the code I have tried.
const data = [
{id: 2, Name:"Task 1.1", ParentID: 1},
{id: 5, Name:"Task 2", ParentID: null},
{id: 6, Name:"Task 2.1", ParentID: 5},
{id: 1, Name:"Task 1", ParentID: null},
{id: 3, Name:"Task 1.2", ParentID: 1},
{id: 4, Name:"Task 1.3", ParentID: 1},
{id: 7, Name:"Task 2.2", ParentID: 5},
{id: 8, Name:"Task 2.3", ParentID: 5},
{id: 9, Name:"Task 3", ParentID: null},
];
function buildHierarchy(data) {
let result = [];
let todos = [...data];
// construct root level
todos.forEach((task)=>{
if(task.ParentID === null){
const new_task = {...task, subtasks:[]}
result.push(new_task)
}
})
// constrct child level
result.forEach((parent, index)=>{
todos.forEach((task)=>{
if(task.ParentID === parent.id){
const new_task = {...task, subtasks:[]}
parent.subtasks.push(new_task)
}
})
})
return result
}
const result = buildHierarchy(data);
console.log(JSON.stringify(result,null,2));
Upvotes: 2
Views: 46
Reputation: 21140
This solution uses 3 collections to keep track of all the data.
rootCollection
(array) holds all tasks that have ParentID
null
(or undefined
), this variable will act as the result.todos
(Map) holds all the todo tasks we've already visited, indexed by id
.subtasks
(Map) stores todo tasks that where visited and have a ParentID
, but the parent has not yet been iterated.With above structure in mind, we iterate each task in the data and follow the following steps:
subtasks
property. If subtasks
variable holds has a value for this task use that value as the subtasks
collection, if not use an empty array.todos
variable.rootCollection
array.todos
, add the current task to the subtasks of the parent.todos
(meaning it has not been iterated yet):
subtasks
variable has a key for the current parent. If there is no key yet, store an empty array as the value.subtasks
variable, so we can use it as subtasks
property once we encounter the parent (see 2).const data = [
{ id: 2, Name:"Task 1.1", ParentID: 1 },
{ id: 5, Name:"Task 2", ParentID: null },
{ id: 6, Name:"Task 2.1", ParentID: 5 },
{ id: 1, Name:"Task 1", ParentID: null },
{ id: 3, Name:"Task 1.2", ParentID: 1 },
{ id: 4, Name:"Task 1.3", ParentID: 1 },
{ id: 7, Name:"Task 2.2", ParentID: 5 },
{ id: 8, Name:"Task 2.3", ParentID: 5 },
{ id: 9, Name:"Task 3", ParentID: null },
];
const rootCollection = [];
const todos = new Map();
const subtasks = new Map();
for (let task of data) {
task = { ...task, subtasks: subtasks.get(task.id) || [] }; // 1 & 2
todos.set(task.id, task); // 3
if (task.ParentID == null) {
rootCollection.push(task); // 4
} else if (todos.has(task.ParentID)) {
todos.get(task.ParentID).subtasks.push(task); // 5
} else {
if (!subtasks.has(task.ParentID)) subtasks.set(task.ParentID, []); // 6.1
subtasks.get(task.ParentID).push(task); // 6.2
}
}
console.log(rootCollection);
The above should work for all nesting levels. Assuming the provided structure has a unique id for each task (not just unique within the parent), and all references to parents are valid. If an parent reference is invalid (because the parent id does not exist) the task will not be included in the result.
Upvotes: 1