There is no SOQL where you can get the top level, its children, and its children's children, etc., because you can't go more than one level deep on the related lists. I think you need to have a SOQL query where you get all of the tasks in which you are interested and their parent and grandparent.
To me, the somewhat less obvious part would be coming up with a solution that avoids using a nested loop. You don't want to go back over the List with every new task processed, looking for its parent to get it's children list to add the task to. If you keep a Map of task Id to TaskHierarchyWrapper, in addition to the list of top level TaskHierarchyWrappers, then you can avoid nested loops.
Here's code that builds a list of TaskHierarchyWrapper that can then be passed to the JSON.serialize method. The basic idea behind the code is pretty simple.
- Process the task.
- Process the parent of the task.
- Add the task to the children list of the parent.
The key is making sure to query for the grand parent (i.e., parent's parent), so that when you are processing a parent you know whether it is a top level (i.e., parent-less) task.
It is possible that a parent task will be added to the hierarchy before it is encountered on the allTasks list (i.e., before it is the main task in the loop). At that point it will not have its parent's fields available, so it won't be added to its parent's list of children. Later, when it is the current task being processed it will already be on the hierarchy, so it won't get added; however, it will add itself to its parent's list of children, because at that point it will have its parent's fields available.
The loop will be executed N times. There's some room in it to reduce script statements if that's needed, but I didn't do that in favor of readability.
Note: I just used the record Id for Task Id and Name for Task Name
public class TaskHierarchyWrapper {
public String TaskId {get; set;}
public String TaskName {get; set;}
public List<TaskHierarchyWrapper> children {get; set;}
public TaskHierarchyWrapper(Project_Task__c task) {
taskId = task.Id;
taskName = task.Name;
this.children = new List<TaskHierarchyWrapper>();
}
public static List<TaskHierarchyWrapper> buildWrappers(List<Project_Task__c> allTasks) {
Map<Id, TaskHierarchyWrapper> taskMap = new Map<Id, TaskHierarchyWrapper>();
List<TaskHierarchyWrapper> topLevelTasks = new List<TaskHierarchyWrapper>();
for (Project_Task__c task : allTasks) {
TaskHierarchyWrapper taskWrapper = processTask(task, taskMap, topLevelTasks);
if (task.Parent_Task__c != null) {
TaskHierarchyWrapper parentTaskWrapper = processTask(task.Parent_Task__r, taskMap, topLevelTasks);
parentTaskWrapper.children.add(taskWrapper);
}
}
return topLevelTasks;
}
private static TaskHierarchyWrapper processTask(
Project_Task__c task,
Map<Id,TaskHierarchyWrapper> taskMap,
List<TaskHierarchyWrapper> topLevelTasks
) {
// The wrapper may already exist if it was processed as the parent
// of another task
TaskHierarchyWrapper wrapper = taskMap.get(task.Id);
if (wrapper == null) {
wrapper = new TaskHierarchyWrapper(task);
taskMap.put(task.Id, wrapper);
// Add to the top level list if there's no parent
// This works for the parent as well because the grandparent
// is included in the original SOQL query
if (task.Parent_Task__c == null) {
topLevelTasks.add(wrapper);
}
}
return wrapper;
}
@isTest
static void testBuildingWrapper() {
setupTasks(); // somehow insert them.
// Get all of the tasks along with their parent and grandparent
List<Project_Task__c> allTasks = [
Select Id, Name,
Parent_Task__c, Parent_Task__r.Id, Parent_Task__r.Name,
Parent_Task__r.Parent_Task__c
From Project_Task__c
];
Test.startTest();
List<TaskHierarchyWrapper> wrappers = buildWrappers(allTasks);
Test.stopTest();
}
}