如何实现无限嵌套层级的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
相关产品推荐
相关产品推荐

