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

如何实现无限嵌套层级的card数据Schema校验?

解决方案:搞定嵌套card结构的Schema校验(避免无限递归炸栈)

核心思路

  • 识别array:card这类复合类型,拆分出数组类型与元素对应的schema引用
  • 用迭代式队列遍历代替递归处理嵌套的parents,避免深层嵌套导致的栈溢出问题
  • 复用同一套card schema,校验所有层级的card对象(包括根节点和parents中的元素)

实现代码

function validate(data, schema) {
  const errors = [];
  
  // 1. 校验当前层级的基础字段(复用你已有的必填、类型校验逻辑)
  const baseErrors = validateBaseFields(data, schema);
  errors.push(...baseErrors);

  // 2. 用队列迭代处理所有嵌套的parents,避免递归栈溢出
  const checkQueue = [];
  // 先把当前对象的parents加入队列(如果存在且是数组)
  if (data.parents && Array.isArray(data.parents)) {
    checkQueue.push(...data.parents);
  }

  while (checkQueue.length > 0) {
    const currentCard = checkQueue.shift();
    // 校验当前嵌套card的基础字段
    const nestedErrors = validateBaseFields(currentCard, schema);
    errors.push(...nestedErrors);

    // 如果当前card还有parents,继续加入队列
    if (currentCard.parents && Array.isArray(currentCard.parents)) {
      checkQueue.push(...currentCard.parents);
    }
  }

  return errors.length === 0 ? true : errors;
}

// 抽离基础字段校验逻辑,根节点和嵌套节点共用
function validateBaseFields(data, schema) {
  const errors = [];
  for (const field of schema) {
    const { name, required, type } = field;
    const value = data[name];

    // 必填项校验
    if (required && (value === undefined || value === null)) {
      errors.push(`字段 "${name}" 为必填项`);
      continue;
    }

    // 跳过array:card类型,嵌套的parents交给队列处理
    if (type.startsWith('array:')) {
      // 先校验当前字段是不是数组
      if (value !== undefined && value !== null && !Array.isArray(value)) {
        errors.push(`字段 "${name}" 必须为card数组`);
      }
      continue;
    }

    // 普通类型校验(number/string等)
    const expectedType = type;
    const actualType = typeof value;
    if (value !== undefined && value !== null && actualType !== expectedType) {
      errors.push(`字段 "${name}" 类型应为${expectedType},实际为${actualType}`);
    }
  }
  return errors;
}

// 测试用例
const data = { 
  cards: [{
    id: 1,
    name: "Paris",
    value: 99,
    parents: [{
      id: 8,
      name: "Parent 1",
      value: 200, 
      parents: [] 
     }, {
      // 构造错误:缺少name字段
      id: 9,
      value: 150,
      parents: [{
        id: 10,
        name: "Grandparent",
        // 构造错误:value是字符串
        value: "not a number",
        parents: []
      }]
     }]
 }] 
}

const schema = [{ 
   name: 'id',
   required: true,
   type: 'number'
 }, { 
  name: 'name',
  required: true,
  type: 'string'
 }, { 
  name: 'value', 
  required: true,
  type: 'number' 
}, { 
  name: 'parents',
  required: false, 
  type: 'array:card' 
}]

// 校验第一个card
console.log(validate(data.cards[0], schema));
// 输出错误信息:
// ["字段 \"name\" 为必填项", "字段 \"value\" 类型应为number,实际为string"]

关键细节说明

  • 队列迭代的优势:通过队列存储待校验的嵌套card,循环处理直到队列为空,完全避免了递归调用的栈溢出问题,哪怕遇到无限嵌套(比如循环引用的parents),也可以轻松添加已校验ID的记录来终止循环
  • 逻辑复用:把基础字段校验抽成独立函数,不管是根card还是嵌套的parent card,都用同一套规则校验,减少冗余代码
  • 复合类型处理:专门识别array:card类型,先校验字段是否为数组,再把数组里的元素加入队列等待后续校验,保证嵌套结构的校验完整性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 10:20:34