Dynamically nesting objects in JSON

javascriptjsonlightning-web-components

I have a use case where I need to dynamically create a JSON.

Below is an idea of what a finished JSON will look like:

var treeData = [{
Name: "Johnson Holding Company",
Id: '1',
children: [
    {Name: "Johnson Metals",
     Id: '2',
     parentId: '1'
    },
    {Name: "Johnson Plastics",
     Id: '3', 
     parentId: '1', 
     children: [
         {Name: "Johnson Little Plastics", 
          Id: '4',
          parentId: '3',
          children: [
              {Name: "Johnson Little Box",
               Id: '5',
               parentId: '4'
              }
            ]
         }]}
]
}];

As you can see the account is related to the parent by the parentId field. I can work out the logic to get a grandchild account nested but the problem I'm having is I need to be able to make a list of the account is for the newest child accounts, see if there are any other children accounts and if there are then nest them where appropriate.

Any help would be much appreciated. Again the biggest thing I need help with is once I get the grandChildren records and add them to the treeData in the next method, How would I loop the logic to keep checking new account ids and seeing if they have children and then add them to the parent account in the treeData JSON until no new accounts are found?

Best Answer

Considering your scenario, I tried creating some tree structure on my dev org. Below is the code snippet for all the components:

dynamicTreeStructure.html

<template>
<lightning-card title="Tree Components with mutliple nested Accounts">
  <div class="slds-m-top_medium slds-m-bottom_x-large">
    <!-- Simple -->
    <template if:true={accounts.data}>
      <div class="slds-p-around_medium lgc-bg">
        <lightning-tree items={accounts.data} header="Accounts"></lightning-tree>
      </div>
    </template>
  </div>
</lightning-card>

dynamicTreeStructure.js

 import { LightningElement, wire } from 'lwc';
 import getAccounts from '@salesforce/apex/DynamicTreeStructureController.getAccounts';

 export default class Dynamic_Tree_Structure extends LightningElement {
   @wire(getAccounts) accounts;
 }

DynamicTreeStructureController.cls

    public with sharing class DynamicTreeStructureController {
    public DynamicTreeStructureController() {

    }

    private static Map<Id, TreeStructure> result;
    private static Map<Id, Id> childIdMap;

    /**
    Static Method to be fed in @wire for LWC
    */
    @AuraEnabled(cacheable=true)
    public static List<TreeStructure> getAccounts(){
        result = new Map<Id, TreeStructure>();
        childIdMap = new Map<Id, Id>();
        Map<Id, Account> accMap = new Map<Id, Account>([SELECT Id, Name FROM Account WHERE ParentId = null]);
        if(!accMap.isEmpty()){
            startFetchingAccountDetails(accMap);
        }
        System.debug(JSON.serialize(result));
        return result.values();
    }

    /**
    * Recursion method to get all levels of accounts and their related records
    */
    private static List<TreeStructure> startFetchingAccountDetails(Map<Id, Account> accMap){
        Map<Id, TreeStructure> parentStructure = gatherAllAccountInformation(accMap);

        //attach the first level to actual result and rest will auotmatically link
        //due to pass by reference way
        if(result == null || result.isEmpty()){
            result.putAll(parentStructure);
        }
        Map<Id, Account> childMap = new Map<Id, Account>([SELECT Id, Name, DemoLight12__Account__c FROM Account WHERE DemoLight12__Account__c =: accMap.keySet()]);
        if(childMap != null && !childMap.isEmpty() && childMap.size() > 0){
            Map<Id, Id> accChildIdMap = new Map<Id, Id>();
            for(Id childAccountId : childMap.keySet()){
                Account child = childMap.get(childAccountId);
                childIdMap.put(child.Id, child.DemoLight12__Account__c);
            }

            //run this method recursively to get all child levels.
            List<TreeStructure> childStructure = startFetchingAccountDetails(childMap);
            for(TreeStructure child : childStructure){
                TreeStructure parent = parentStructure.get(childIdMap.get(child.name));
                parent.items.add(child);
            }
        }
        return parentStructure.values();
    }

    /**
    * Method to gather all information for all accounts recieved
    */
    private static Map<Id, TreeStructure> gatherAllAccountInformation( Map<Id, Account> accMap){
        Map<Id, TreeStructure> result = new Map<Id, TreeStructure>();

        Map<Id, List<Contact>> accConMap = new  Map<Id, List<Contact>>();
        Map<Id, List<Opportunity>> accOppCMap = new Map<Id, List<Opportunity>>();
        Map<Id, List<Case>> conCaseCMap = new Map<Id, List<Case>>();

        //gather all contacts
        for(Contact con : [SELECT Id, Name, AccountId FROM Contact WHERE AccountId =: accMap.keySet()]){
            if(!accConMap.containsKey(con.AccountId)){
                accConMap.put(con.AccountId, new List<Contact>());
            }
             accConMap.get(con.AccountId).add(con);
        }

        //gather all cases
        for(Case cas : [SELECT Id, CaseNumber, ContactId FROM Case WHERE ContactId =: accConMap.keySet()]){
            if(!conCaseCMap.containsKey(cas.ContactId)){
                conCaseCMap.put(cas.ContactId, new List<Case>());
            }
            conCaseCMap.get(cas.ContactId).add(cas);
        }

        for(Id accountId : accMap.keySet()){
            Account acc = accMap.get(accountId);
            TreeStructure accStructure = new TreeStructure(acc.name, accountId, false, null);

            //add all contacts if present
            if(accConMap.containsKey(accountId)){
                TreeStructure conStructure = new TreeStructure('Contacts', 'Contacts', false, null);
                for(Contact con :  accConMap.get(accountId)){
                    conStructure.items.add( new TreeStructure(con.Name, con.Id, false, null));
                    if(conCaseCMap.containsKey(con.Id)){
                        TreeStructure caseStructure = new TreeStructure('Cases', 'Cases', false, null);
                        for(Case cas : conCaseCMap.get(con.Id)){
                            caseStructure.items.add( new TreeStructure(cas.CaseNumber, cas.Id, false, null));
                        }
                        conStructure.items.add(caseStructure);
                    }
                }
                accStructure.items.add(conStructure);
            }

            result.put(accountId, accStructure);
        }
        return result;
    }
}

Wrapper - TreeStructure.cls

    public class TreeStructure{
    @AuraEnabled public String label;
    @AuraEnabled public String name;
    @AuraEnabled public Boolean expanded;
    @AuraEnabled public List<TreeStructure> items;
    public TreeStructure(String label, String name, Boolean expanded, List<TreeStructure> items){
        this.label = label;
        this.name = name;
        this.expanded = expanded;
        if(items != null && items.size() > 0){
            this.items = items;
        }else{
            this.items = new List<TreeStructure>();
        }
    }
}

enter image description here

Hope, I understood your use case properly and this helps you.

Related Topic