1

Given a flat array of objects, I group the children items by pid (parent id) to the id's of the parents. My function does exactly that, but only two levels deep. How could I turn this function into a function that handles nesting of any depth?

This is the data:

const list = 
 [   
     { "pid": 0, "id": "solo"},
     { "pid": 0, "id": "member"},
     { "pid": 0, "id": "solo2"},
     { "pid": "member", "id": "10_admin_members"},
     { "pid": "member", "id": "10_invitations"},
     { "pid": "member", "id": "sub_member"},
     { "pid": "sub_member", "id": "other"},
     { "pid": "sub_member", "id": "other2"},
     { "pid": 0, "id": "admin"},
     { "pid": "admin", "id": "admin_general"},
     { "pid": "admin", "id": "admin_modules"},
     { "pid": "admin", "id": "admin_roles"},
     { "pid": "admin", "id": "admin_navigation"},
 ]

And the function:

const groupChildrenToParentsByKeys = (firstLevelItems, groupedItems, childArrayKey = 'items') 
 => {
  let cleanArray = []
  for (const a in firstLevelItems) {
      let childrenCount = 0
      for (const b in groupedItems) {
          if(firstLevelItems[a].id == b) {
              let descendantsArray = []
              let childrenArray = groupedItems[b]

                for (const c in childrenArray) {
                  let grandChildrenCount = 0
                  for (const h in groupedItems) {
                    if(childrenArray[c].id == h) {
                      let grandChildrenArray = groupedItems[h]
                      descendantsArray.push({...childrenArray[c], [childArrayKey]: grandChildrenArray})
                      grandChildrenCount ++
                    } 
                  }
                  if(!grandChildrenCount) {
                    descendantsArray.push(childrenArray[c])
                  }
                }
              cleanArray.push({...firstLevelItems[a], [childArrayKey]: descendantsArray})
              childrenCount ++
          }
      } 
      if(!childrenCount) {
        cleanArray.push(firstLevelItems[a])
      }
  }
  return cleanArray

}

const list = 
 [   
     { "pid": 0, "id": "solo"},
     { "pid": 0, "id": "member"},
     { "pid": 0, "id": "solo2"},
     { "pid": "member", "id": "10_admin_members"},
     { "pid": "member", "id": "10_invitations"},
     { "pid": "member", "id": "sub_member"},
     { "pid": "sub_member", "id": "other"},
     { "pid": "sub_member", "id": "other2"},
     { "pid": 0, "id": "admin"},
     { "pid": "admin", "id": "admin_general"},
     { "pid": "admin", "id": "admin_modules"},
     { "pid": "admin", "id": "admin_roles"},
     { "pid": "admin", "id": "admin_navigation"},
 ]

// Matches two objects by keys and inserts the children inside of parents as array
const groupChildrenToParentsByKeys = (firstLevelItems, groupedItems, childArrayKey = 'items') => {
    let cleanArray = []
    for (const a in firstLevelItems) {
        let childrenCount = 0
        for (const b in groupedItems) {
            if(firstLevelItems[a].id == b) {
                let descendantsArray = []
                let childrenArray = groupedItems[b]
                
                  for (const c in childrenArray) {
                    let grandChildrenCount = 0
                    for (const h in groupedItems) {
                      if(childrenArray[c].id == h) {
                        let grandChildrenArray = groupedItems[h]
                        descendantsArray.push({...childrenArray[c], [childArrayKey]: grandChildrenArray})
                        grandChildrenCount ++
                      } 
                    }
                    if(!grandChildrenCount) {
                      descendantsArray.push(childrenArray[c])
                    }
                  }
                cleanArray.push({...firstLevelItems[a], [childArrayKey]: descendantsArray})
                childrenCount ++
            }
        } 
        if(!childrenCount) {
          cleanArray.push(firstLevelItems[a])
        }
    }
    return cleanArray
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let groupedObject = _.groupBy(list, 'pid') 
let firstLevel = {...groupedObject}[0]
let childrenParentArray = groupChildrenToParentsByKeys(firstLevel, groupedObject)
console.log(JSON.stringify(childrenParentArray, null, 2))
console.log(_.size(childrenParentArray))
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
Hexodus
  • 12,361
  • 6
  • 53
  • 72

1 Answers1

1

Take a single loop and create a relation for parent/children and children/parent at the same time. As result, you get a tree which rely on the relations but not on the order of the data.

const
    getTree = (data, root) => {
    const t = {};
        data.forEach(o => 
            ((t[o.pid] ??= {}).items ??= []).push(Object.assign(t[o.id] ??= {}, o))
        );
        return t[root].items;
    },
    list = [{ pid: 0, id: "solo" }, { pid: 0, id: "member" }, { pid: 0, id: "solo2" }, { pid: "member", id: "10_admin_members" }, { pid: "member", id: "10_invitations" }, { pid: "member", id: "sub_member" }, { pid: "sub_member", id: "other" }, { pid: "sub_member", id: "other2" }, { pid: 0, id: "admin" }, { pid: "admin", id: "admin_general" }, { pid: "admin", id: "admin_modules" }, { pid: "admin", id: "admin_roles" }, { pid: "admin", id: "admin_navigation" }],
    tree = getTree(list, 0);

console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392