Home > OS >  How can i recursively group children objects under parent in an array of objects in JavaScript?
How can i recursively group children objects under parent in an array of objects in JavaScript?

Time:08-05

I have an array of topics each linked to a parent topic. So, basically what I want to do is form an array with all the children topics nested under the parent. So if I had an table of topics looking like this:

id name parent_id
1 Topic 1 null
2 Topic 2 1
3 Topic 3 2
4 Topic 4 2
5 Topic 5 4

And if the json array looks like this:

[
  { id: 1, name: "Topic 1", parent_id: null },
  { id: 2, name: "Topic 2", parent_id: 1 },
  { id: 3, name: "Topic 3", parent_id: 2 },
  { id: 4, name: "Topic 4", parent_id: 2 },
  { id: 5, name: "Topic 5", parent_id: 4 },
]

I want the output array to look like this:

[
  {
    id: 1,
    name: "Topic 1",
    children: [
      {
        id: 2,
        name: "Topic 2",
        children: [
          { id: 3, name: "Topic 3", children: [] },
          {
            id: 4,
            name: "Topic 4",
            children: [{ id: 5, name: "Topic 5", children: [] }],
          },
        ],
      },
    ],
  },
]

CodePudding user response:

We can achieve this with a simple recursive function and iterate from the top of your list of objects.

function BuildChild(data, currentChild){
//Creating current child object
var child = {};
child.id = currentChild.id;
child.name = currentChild.name;
child.children = [];

//Looking for childrens in all input data
var currentChildren = data.filter(item => item.parent_id == child.id);
if(currentChildren.length > 0){
    currentChildren.forEach(function(item){
        //Iterating threw children and calling the recursive function
        //Adding the result to our current children
        child.children.push(BuildChild(data,item));
    });
}
return child;  }

You can then call it while getting the "top parent" of your list :

BuildChild(data, data.find(child => child.parent_id == null))

Snippet with result :

 function BuildChild(data, currentChild){
    //Creating current child object
    var child = {};
    child.id = currentChild.id;
    child.name = currentChild.name;
    child.children = [];

    //Looking for childrens in all input data
    var currentChildren = data.filter(item => item.parent_id == child.id);
    if(currentChildren.length > 0){
        currentChildren.forEach(function(item){
            //Iterating threw children and calling the recursive function
            //Adding the result to our current children
            child.children.push(BuildChild(data,item));
        });
    }
    return child;  
 }
 
 
 var data = [
  { id: 1, name: "Topic 1", parent_id: null },
  { id: 2, name: "Topic 2", parent_id: 1 },
  { id: 3, name: "Topic 3", parent_id: 2 },
  { id: 4, name: "Topic 4", parent_id: 2 },
  { id: 5, name: "Topic 5", parent_id: 4 },
]
console.log(BuildChild(data, data.find(child => child.parent_id == null)))

CodePudding user response:

Get which IDs are at the root level, i.e. with no parents. For those IDs, recursively build their grouped data, similar to writing a depth-first search recursion.

Reading the code will probably be easier:

// Helper function, returns {ID -> set of child IDs}
function getAdjMatrix(data) {                                                                                                                                                               
  const adjMatrix = new Map();                                                                                                                      
  for (const entry of data) {                                                                                                                                                               
    if (!adjMatrix.has(entry.id)) {                                                                                                                                                         
      adjMatrix.set(entry.id, new Set());                                                                                                                                                   
    }                                                                                                                                                                                       
    if (entry.parent_id !== null) {                                                                                                                                                         
      if (!adjMatrix.has(entry.parent_id)) {                                                                                                                                                
        adjMatrix.set(entry.parent_id, new Set());                                                                                                                                          
      }                                                                                                                                                                                     
      adjMatrix.get(entry.parent_id).add(entry.id);                                                                                                                                         
    }                                                                                                                                                                                       
  }                                                                                                                                                                                         
  return adjMatrix;                                                                                                                                                                         
}                                                                                                                                                                                           

// Helper function, returns grouped data for the given ID                                                                                                                                                                                          
function getGroupedDataForId(id, idToData, adjMatrix, processedIds) {                                                                                                                     
  if (!idToData.has(id) || !adjMatrix.has(id) || processedIds.has(id)) {                                                                                                                    
    return null;                                                                                                                                                                            
  }                                                                                                                                                                                         
  const result = idToData.get(id);                                                                                                                                                          
  result.children = [];                                                                                                                                                                     
  // Add children                                                                                                                                                                           
  for (const childId of adjMatrix.get(id)) {                                                                                                                                                
    const childIdData = getGroupedDataForId(                                                                                                                                              
        /*id=*/ childId, idToData, adjMatrix, processedIds);                                                                                                                                
    if (childIdData !== null) {                                                                                                                                                             
      result.children.push(childIdData);                                                                                                                                                    
    }                                                                                                                                                                                       
    processedIds.add(childId);                                                                                                                                                              
  }                                                                                                                                                                                         
  processedIds.add(id);                                                                                                                                                                     
  return result;                                                                                                                                                                            
}                                                                                                                                                                                    

// Returns the final grouped data                                                                                                                                                                                       
function getGroupedData(data) {                                                                                                                                                              
  // A map of ID to its data                                                                                                                                                                
  const idToData = new Map(data.map(entry => [entry.id, entry]));                                                                                                                           
  // Adjacency matrix                                                                                                                                                                       
  // Represented by {id -> set of children}                                                                                                                                                 
  const adjMatrix = getAdjMatrix(data);                                                                                                                                                     
                                                                                                                                                                                            
  // Root IDs (no parents)                                                                                                                                                                  
  const rootIds =                                                                                                                                                                           
      data.filter(entry => entry.parent_id === null).map(entry => entry.id);                                                                                                                
  // IDs that have been grouped with children                                                                                                                                               
  const processedIds = new Set();                                                                                                                                                           
  // Final result                                                                                                                                                                           
  const result = [];                                                                                                                                                                        
                                                                                                                                                                                            
  // Recursively add IDs                                                                                                                                                                    
  for (const rootId of rootIds) {                                                                                                                                                           
    groupedData = getGroupedDataForId(                                                                                                                                                    
        /*id=*/ rootId, idToData, adjMatrix, processedIds);                                                                                                                                 
    if (groupedData !== null) {                                                                                                                                                             
      result.push(groupedData);                                                                                                                                                             
    }                                                                                                                                                                                       
  }                                                                                                                                                                                         
                                                                                                                                                                                            
  return result;                                                                                                                                                                            
}                                                                                                                                                                                           


// Test
const data = [                                                                                                                                                                              
  {'id': 1, 'name': 'Topic 1', 'parent_id': null},                                                                                                                                          
  {'id': 2, 'name': 'Topic 2', 'parent_id': 1},                                                                                                                                             
  {'id': 3, 'name': 'Topic 3', 'parent_id': 2},                                                                                                                                             
  {'id': 4, 'name': 'Topic 4', 'parent_id': 2},                                                                                                                                             
  {'id': 5, 'name': 'Topic 5', 'parent_id': 4},                                                                                                                                             
  {'id': 6, 'name': 'Topic 6', 'parent_id': null},                                                                                                                                          
  {'id': 7, 'name': 'Topic 7', 'parent_id': 1},                                                                                                                                             
];

console.log(JSON.stringify(getGroupedData(data)));
  • Related