0

Please help me write a method for finding child objects. The method receives the name of the object and an array of all possible objects. I need to find an object by the input name in this spike, then see what child objects it has, then go through each object in the same way. Thus, you need to find all the children throughout the cascade. The resulting file is this.ArchiveObject[]. The method I wrote "findChildObjects" goes to infinity and doesn't work correctly.

public async execute(): Promise<void> {
const templateExecutionSteps = await this.getTemplateExecutionSteps();
// Get global describe
const globalDescribe = await SfDescribe.objects(this.auth);
await this.backup.sobjectKeyPrefix(globalDescribe);

// Get names of user-selected objects
const objectsName = [...templateExecutionSteps.keys()].map(objectName => {
  const describeObject = globalDescribe.sobjects.find(describeObj => describeObj.name === objectName);
  if (!describeObject) throw new Error(`Object ${objectName} not found`);
  if (!describeObject.queryable) throw new Error(`Object ${objectName} not queryable`);
  return describeObject;
});
// Get names of all objects from org
const globalDescribeNames: string[] = [];
globalDescribe.sobjects.forEach(obj => globalDescribeNames.push(obj.name));
// Get global describe for all objects from org
const describeAllObjects = await SfCompositeUtils.describeObjects(this.auth, globalDescribeNames, true);
// Push global describe for all objects result in map
const globalDescribeMap: Map<string, SFDescribeObjects> = new Map();
describeAllObjects.forEach(objectDescribe => globalDescribeMap.set(objectDescribe.name, objectDescribe));
this.objectsArray = [];
objectsName.forEach(object => {
  const allObjects = this.findChildObjects(object.name, globalDescribeMap);
});

}

public findChildObjects(selectedObject: string, globalDescribeMap: Map<string, SFDescribeObjects>): any {
const currentObject: ArchiveObject = {};
console.log(globalDescribeMap);
globalDescribeMap.get(selectedObject).childRelationships.forEach(object => {
  if (
    this.objectsArray.find(item => item.objectName === object.childSObject && item.referenceField === object.field)
  ) {
    return;
  }
  currentObject.referenceTo = selectedObject;
  currentObject.objectName = object.childSObject;
  currentObject.referenceField = object.field;
  if (object.cascadeDelete || object.restrictedDelete) {
    currentObject.isDelete = true;
    this.objectsArray.push(currentObject);
    const result = this.findChildObjects(object.childSObject, globalDescribeMap);
    this.objectsArray.push(...result);
  } else {
    currentObject.isDelete = false;
    this.objectsArray.push(currentObject);
  }
});
return this.objectsArray;

}

export interface ArchiveObject {objectName?: string; referenceField?: string; referenceTo?: string; isDelete?: boolean;}

Here is the globalDescribeMap structure:

'DashboardFeed' => {
actionOverrides: [],
activateable: false,
associateEntityType: 'Feed',
associateParentEntity: 'Dashboard',
childRelationships: [
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object]
],
compactLayoutable: false,
createable: false,
custom: false,
customSetting: false,
deepCloneable: false,
defaultImplementation: null,
deletable: true,
deprecatedAndHidden: false,
extendedBy: null,
extendsInterfaces: null,
feedEnabled: false,
fields: [
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object], [Object],
  [Object]
],
hasSubtypes: false,
implementedBy: null,
implementsInterfaces: null,
isInterface: false,
isSubtype: false,
keyPrefix: null,
label: 'Dashboard Feed',
labelPlural: 'Dashboard Feed',
layoutable: false,
listviewable: null,
lookupLayoutable: null,
mergeable: false,
mruEnabled: false,
name: 'DashboardFeed',
namedLayoutInfos: [],
networkScopeFieldName: null,
queryable: true,
recordTypeInfos: [],
replicateable: true,
retrieveable: true,
searchLayoutable: false,
searchable: false,
sobjectDescribeOption: 'FULL',
supportedScopes: [ [Object] ],
triggerable: false,
undeletable: false,
updateable: false,
urls: {
  rowTemplate: '/services/data/v50.0/sobjects/DashboardFeed/{ID}',
  describe: '/services/data/v50.0/sobjects/DashboardFeed/describe',
  sobject: '/services/data/v50.0/sobjects/DashboardFeed'
}

},

Visual structure of one object

1
  • 1
    Please trim your code to make it easier to find your problem. Follow these guidelines to create a minimal reproducible example. Commented Nov 6, 2021 at 18:19

1 Answer 1

2

I think the problem could be that there are circular dependencies.

Your data model can appear to be a tree, while it is actually more like a graph. If you apply recursive search algorithms to traverse through a graph, you can get stuck in an endless loop. Due to the circular references, it would be like searching in a tree of endless depth.

The solution, is to never process an element twice. To accomplish this, you need to keep track of a collection of processed elements, and check whether they were already processed before.


Your findChildObjects function could use a Set to mark/verify that objects have been processed.

public findChildObjects(selectedObject,..., alreadyProcessed: Set) {
  // make sure objects are processed only once
  if (alreadyProcessed.has(selectedObject)) return [];
  alreadyProcessed.add(selectedObject);

  ...
}

Of course, you would need to create the set in advance before calling it the first time.

const alreadyProcessed = new Set();
...
this.findChildObjects(..., ..., alreadyProcessed);

Basically, whenever you call the this.findChildObjects(...) you pass along the collection, which makes it possible to detect and skip objects that already have been processed.

Sign up to request clarification or add additional context in comments.

2 Comments

Well, a tree with circular dependencies isn't a tree, but rather a graph, no? But indeed, that's a problem we can find in Dom objects, for example.
@PhiLho I rephrased that a little. :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.