Background: Long story short, my company is integrating with a platform that has a "Role Hierarchy", so to speak, but that Hierarchy isn't stored in Salesforce. To get the "roles" I have to query their API. Even further than that, each Role can have child Roles, but the only way to find that out is to individually query each Role via their API to see if it has any children. Those children can have children, and those children can have children, etc. In total, with our current setup, it is possible that the parent-child roles could go as far as 6 levels deep.
SO, I think I need to use a 6 level deep nested Map of Strings, like this:
Map<String,Map<String,Map<String,Map<String,Map<String,Map<String,String>>>>>> allLevels = new Map<String,Map<String,Map<String,Map<String,Map<String,Map<String,String>>>>>>();
First question: Is this even possible? Am I exceeding any limits with a Map like this? I have looked over documentation and can't find anything regarding nested map limits.
When I am querying their API, it looks like this:
https://api.website.com/v1/teams?apikey=API-KEY-HERE&source=Salesforce-GETTeams
This returns JSON results like this:
{
"Id": "pAFwnPpqJTo1",
"Name": "External",
"TeamCodeForBulkImport": "446878-External"
},
{
"Id": "R7tu6KPYk6k1",
"Name": "English",
"TeamCodeForBulkImport": "446885-English"
},
{
"Id": "-7tu-ZPY3Ik1",
"Name": "Spanish",
"TeamCodeForBulkImport": "446885-Spanish"
},
{
"Id": "-TwdJwJzjbY1",
"Name": "Application",
"TeamCodeForBulkImport": "446904-Application"
},
{
"Id": "-Twdzz-jbY1",
"Name": "Enterprise",
"TeamCodeForBulkImport": "446904-Enterprise"
},
{
"Id": "-TWd2w-fXY1",
"Name": "Enterprise",
"TeamCodeForBulkImport": "463321-Enterprise"
},
It returns ALL teams, from ALL levels (top level, children, grandchildren, etc) using this query. Note that Enterprise is listed multiple times, that is because Enterprise exists more than once as a child to different parents. In my case, I need to know the true hierarchy which looks something like this:
v-- External --v
v-- Spanish English --v
v-- Application Application --v
v-- Enterprise Enterprise --v
To get the actual children, I need to do a separate API call using the ID of the Parent, like this:
https://api.website.com/v1/teams/TEAM-ID-HERE/teams?apikey=API-KEY-HERE&source=Salesforce-GETTeams
This will return JSON of ONLY the children of whatever TEAM-ID I specified in my call. So the first thing I want to do is make the "top level" categories (Internal/External) the first String in my map, then loop through those IDs to get the children of each and store them in the "second level" of my String Map, then loop through those SECOND LEVEL IDs and get the children of them and store them in the "third level" of my String map, etc etc, until I've traversed all 6 levels and have all of the data stored in my map.
My goal here is to have a Map where the primary keySet is Internal or External, and then the second keySet is the children of Internal or External, and the third keySet is the children of the second keyset, and so on until I've reached the end. My result should be a very elaborate map that I can then use in combination with data sourced from Contact records to determine which group to assign them to, and get that groups ID from this nested map.
Second question: Since I will be storing data incrementally in the map (as detailed above), I need to store data in the map as I get it, and continue to build the map as I go through the loops and make the calls. I've never dealt with a nested map before, especially not a 6-level-deep nested map. How would I properly insert data into the map for the second level while maintaining the third, fourth, fifth, and sixth levels as null, and then move on to storing data in the third level while keeping the fourth, fifth, and sixth null, and so on, until the map is complete?
--- FINAL WORKING CODE ---
After a back-and-forth with sfdcfox, I ended up creating a wrapper class that contained the String id,parentid,name;
of the CustomRole. It also contained a Map<String,CustomRole> childRoles
. I start by doing an API call to get ALL roles and store them in Map<String,CustomRole> roleMap
, then I loop through the values in roleMap
to do the individual sub-team API calls, and then I pull the current Role's childRoles
map from the roleMap
, and add to it for each child role I get back in my sub-team API call.
This results in a Map<String,CustomRole>
where I can loop through the main role and then do an inner loop for its childRoles.
public with sharing class FutureCaller {
private static final String apikey = 'xxxxx-xx-x-xxxx-x';
private static final String endpoint = 'https://api.website.com/v1.svc';
private LitmosFutureCaller() {}
public class CustomRole{
public String guid, parentId, name;
public Map<String, CustomRole> childRoles;
public CustomRole(String teamId,String parentId,String name){
this.guid = teamId;
this.parentId = parentId;
this.name = name;
this.childRoles = new Map<String,CustomRole>();
}
}
@future (callout=true)
public static void getTeams(Set<Id> contactIds, Boolean isNew){
List<Contact> newList = [SELECT Id,Primary_Language__c,Activated__c,LoginKey__c,lmsID__c,Title__c FROM Contact WHERE ID in :contactIds];
// create our roleMap to populate with role Id,CustomRole class
Map<String, CustomRole> roleMap = new Map<String, CustomRole>();
// create source string for use in API calls
String source;
// Strings to hold Id and Name for each object in the parser
String teamId;
String teamName;
// set source string for use in API call
source = 'MSI-Apex-getTeams';
// build HTTP GET Request with JSON Return
HttpRequest httpReq = new HttpRequest();
httpReq.setMethod('GET');
httpReq.setHeader('Accept','application/json');
httpReq.setEndpoint(endpoint+'/teams?apikey='+apikey+'&source='+source);
// create HTTP for Callout
Http http = new Http();
// first we need to build the roleMap by making an initial API call
// to get all teams(roles). Their Id will be the key, and the
// CustomRole wrapper will be the value
try{
// Get HTTP Callout Response
HTTPResponse httpResp = http.send(httpReq);
// Create Parser
JSONParser parser = JSON.createParser(httpResp.getBody());
while(parser.nextToken() != null){
// if Current Token is the Id field, go to Next Token and set teamId
if((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
(parser.getText() == 'Id')){
parser.nextToken();
teamId = parser.getText();
}
// if Current Token is the Name field, go to Next Token and set teamName
if((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
(parser.getText() == 'Name')){
parser.nextToken();
teamName = parser.getText();
}
// once we reach the end of the object...
if((parser.getCurrentToken() == JSONToken.END_OBJECT)){
// If the roleMap doesn't contain the key (teamId)
if(!roleMap.containsKey(teamId)){
// add the related Id to the String,CustomRole(guid,parentId,name) map
roleMap.put(teamId,new CustomRole(teamId,null,teamName));
}
// then reset the team Id and team Name to null before next object in the parser
teamId = null;
teamName = null;
}
}
System.debug('Final Top Level KeySet: ' + roleMap.keySet());
System.debug('Final Top Level Values: ' + roleMap.values());
}catch(System.CalloutException e){
System.debug('Callout Exception on GET TOP LEVEL' + e);
}
// change source for next call
source = 'MSI-Apex-buildSubTeams';
// for each role that was added to the roleMap in the first call
for(CustomRole currentRole : roleMap.values()){
// set a dynamic endpoint to get the subteam based on currentRole.guid
httpReq.setEndpoint(endpoint+'/teams/'+ currentRole.guid +'/teams?apikey='+apikey+'&source='+source);
try{
// Get HTTP Callout Response
HTTPResponse httpResp = http.send(httpReq);
// Create Parser
JSONParser parser = JSON.createParser(httpResp.getBody());
while(parser.nextToken() != null){
// if Current Token is the Id field, go to Next Token and set teamId
if((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
(parser.getText() == 'Id')){
parser.nextToken();
teamId = parser.getText();
}
// if Current Token is the Name field, go to Next Token and set teamName
if((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
(parser.getText() == 'Name')){
parser.nextToken();
teamName = parser.getText();
}
// once we reach the end of the object...
if((parser.getCurrentToken() == JSONToken.END_OBJECT)){
// If the topLevelToIdMap contains the key (Internal or External)
if(roleMap.containsKey(currentRole.guid)){
roleMap.get(currentRole.guid).childRoles.put(teamId,new CustomRole(teamId,currentRole.guid,teamName));
}
// then reset the team Id and team Name to null before next object in the parser
teamId = null;
teamName = null;
}
}
}catch(System.CalloutException e){
System.debug('Callout Exception on GET SUBTEAM LEVEL' + e);
}
}
System.debug('Final SubTeam Level KeySet: ' + roleMap.keySet());
System.debug('Final SubTeam Level Values: ' + roleMap.values());
}
}