You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript递归查找级联子对象方法无限递归问题修复求助

问题根源

你现有代码出现无限递归和数据异常的核心原因有3个:

  • 全程复用同一个currentObject对象引用,导致数组中存储的所有条目都会被后续循环的值覆盖
  • 去重逻辑仅判断已存入全局数组的条目,未处理「A→B→A」这类循环引用场景,子对象还未存入数组就已经触发递归,就会进入死循环
  • 递归返回的是全局objectsArray本身,每次展开...result会重复推送大量重复数据,同时全局数组跨多个根对象共享,会导致多入口查询结果互相污染

修复后代码

1. 修改findChildObjects方法

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

public findChildObjects(
  selectedObject: string,
  globalDescribeMap: Map<string, SFDescribeObjects>,
  // 递归内部传递的已访问标记,外部调用无需传参
  visited = new Set<string>()
): ArchiveObject[] {
  const result: ArchiveObject[] = [];
  const objectDescribe = globalDescribeMap.get(selectedObject);
  if (!objectDescribe) return result;

  objectDescribe.childRelationships.forEach(childRel => {
    // 用「子对象名+关联字段」作为唯一标识,避免同一关联重复处理
    const relKey = `${childRel.childSObject}###${childRel.field}`;
    if (visited.has(relKey)) return;
    visited.add(relKey);

    // 每次新建独立实例,避免引用复用导致的数值覆盖
    const archiveItem: ArchiveObject = {
      referenceTo: selectedObject,
      objectName: childRel.childSObject,
      referenceField: childRel.field,
      isDelete: childRel.cascadeDelete || childRel.restrictedDelete
    };
    result.push(archiveItem);

    // 仅对需要级联删除的对象递归查询下一级子对象
    if (archiveItem.isDelete) {
      const childResult = this.findChildObjects(childRel.childSObject, globalDescribeMap, visited);
      result.push(...childResult);
    }
  });

  return result;
}

2. 修改execute方法中的调用逻辑

public async execute(): Promise<void> {
  const templateExecutionSteps = await this.getTemplateExecutionSteps();
  // 获取全局描述
  const globalDescribe = await SfDescribe.objects(this.auth);
  await this.backup.sobjectKeyPrefix(globalDescribe);

  // 获取用户选中的对象名称
  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;
  });
  // 获取组织内所有对象的名称
  const globalDescribeNames: string[] = [];
  globalDescribe.sobjects.forEach(obj => globalDescribeNames.push(obj.name));
  // 获取组织内所有对象的全局描述
  const describeAllObjects = await SfCompositeUtils.describeObjects(this.auth, globalDescribeNames, true);
  // 将所有对象的全局描述结果存入Map
  const globalDescribeMap: Map<string, SFDescribeObjects> = new Map();
  describeAllObjects.forEach(objectDescribe => globalDescribeMap.set(objectDescribe.name, objectDescribe));
  
  // 替换原有调用逻辑,避免全局数组污染
  this.objectsArray = [];
  for (const object of objectsName) {
    const childObjects = this.findChildObjects(object.name, globalDescribeMap);
    this.objectsArray.push(...childObjects);
  }
}

核心修改说明

  • 新增visited局部集合,第一次访问关联关系就做标记,从根源避免循环递归
  • 每次循环都新建独立的ArchiveObject实例,不会出现引用复用导致的数值覆盖问题
  • 递归返回值改为当前层级的查询结果数组,不再复用全局数组,避免重复推送数据
  • 全局数组仅在根入口做结果汇总,多个根对象的查询结果互不干扰
  • 保留了你原有仅对级联删除/限制删除的对象做深层递归的逻辑,符合需求场景

内容的提问来源于stack exchange,提问作者vgusakov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 03:54:03