如何初始化互相引用的变量?解决AJV验证Schema循环引用报错
解决循环引用导致的Schema初始化错误问题
问题背景
使用TypeScript搭配validate-value(底层基于AJV)构建类似MongoDB运算符的过滤器验证Schema时,由于支持嵌套结构,出现循环引用导致初始化报错:
ReferenceError: Cannot access 'filterSchema' before initialization
无论是将Schema拆分到单独文件,还是合并到单个文件,问题都存在。同时普通JS对象的循环引用(如下方示例)也需要解决:
const foo = { x: bar }; const bar = { y: foo }; console.log(foo);
复现代码
package.json
{ "type": "module", "scripts": { "dev": "node main.js" } }
main.js
import { filterSchema } from "./filterSchemas/filterSchema.js"; console.log(filterSchema);
filterSchemas/filterSchema.js
import { logicalOperatorSchema } from "./logicalOperatorSchema.js"; const filterSchema = { type: "object", oneOf: [logicalOperatorSchema], }; export { filterSchema };
filterSchemas/logicalOperatorSchema.js
import { notSchema } from "./notSchema.js"; const logicalOperatorSchema = { type: "object", oneOf: [notSchema], }; export { logicalOperatorSchema };
filterSchemas/notSchema.js
import { filterSchema } from "./filterSchema.js"; const notSchema = { type: "object", properties: { $not: filterSchema, }, required: ["$not"], additionalProperties: false, }; export { notSchema };
合并到单个文件的尝试(schemas.js)
const notSchema = { type: "object", properties: { $not: filterSchema, }, required: ["$not"], additionalProperties: false, }; const logicalOperatorSchema = { type: "object", oneOf: [notSchema], }; const filterSchema = { type: "object", oneOf: [logicalOperatorSchema], }; export { filterSchema }
解决方法
方法1:利用AJV的JSON Schema引用机制(推荐)
AJV原生支持JSON Schema的$ref和$id特性,通过给每个Schema分配唯一ID,然后用引用代替直接对象赋值,从根本上避免循环引用问题。
单个文件实现
const notSchema = { $id: "#notSchema", type: "object", properties: { $not: { $ref: "#filterSchema" }, }, required: ["$not"], additionalProperties: false, }; const logicalOperatorSchema = { $id: "#logicalOperatorSchema", type: "object", oneOf: [{ $ref: "#notSchema" }], }; const filterSchema = { $id: "#filterSchema", type: "object", oneOf: [{ $ref: "#logicalOperatorSchema" }], }; export { filterSchema };
分文件实现
给每个Schema文件的Schema对象添加$id,然后在初始化AJV实例时加载所有Schema:
- filterSchema.js
export const filterSchema = { $id: "filterSchema", type: "object", oneOf: [{ $ref: "logicalOperatorSchema" }], };
- logicalOperatorSchema.js
export const logicalOperatorSchema = { $id: "logicalOperatorSchema", type: "object", oneOf: [{ $ref: "notSchema" }], };
- notSchema.js
export const notSchema = { $id: "notSchema", type: "object", properties: { $not: { $ref: "filterSchema" }, }, required: ["$not"], additionalProperties: false, };
- 初始化AJV时加载所有Schema
import Ajv from "ajv"; import { filterSchema } from "./filterSchemas/filterSchema.js"; import { logicalOperatorSchema } from "./filterSchemas/logicalOperatorSchema.js"; import { notSchema } from "./filterSchemas/notSchema.js"; const ajv = new Ajv(); ajv.addSchema([filterSchema, logicalOperatorSchema, notSchema]); const validate = ajv.compile(filterSchema);
方法2:延迟赋值(适用于普通JS对象和Schema)
先声明空对象或不包含循环引用的基础结构,再补全循环引用的属性,避免初始化阶段的变量未定义问题。
普通JS对象循环引用解决
// 先声明变量,不赋值循环引用的属性 let foo = {}; let bar = {}; // 延迟赋值循环引用 foo.x = bar; bar.y = foo; console.log(foo); // 正常输出,无报错
Schema的延迟赋值实现(单个文件)
// 先创建基础结构,不包含循环引用 const notSchema = { type: "object", properties: {}, required: ["$not"], additionalProperties: false, }; const logicalOperatorSchema = { type: "object", oneOf: [], }; const filterSchema = { type: "object", oneOf: [], }; // 补全循环引用的属性 notSchema.properties.$not = filterSchema; logicalOperatorSchema.oneOf.push(notSchema); filterSchema.oneOf.push(logicalOperatorSchema); export { filterSchema };
方法3:用函数返回Schema(分文件场景)
将每个Schema包装成函数,调用函数时才生成Schema对象,这样在函数执行时所有引用的模块已经完成初始化,避免循环引用报错。
- filterSchema.js
import { getLogicalOperatorSchema } from "./logicalOperatorSchema.js"; export const getFilterSchema = () => ({ type: "object", oneOf: [getLogicalOperatorSchema()], });
- logicalOperatorSchema.js
import { getNotSchema } from "./notSchema.js"; export const getLogicalOperatorSchema = () => ({ type: "object", oneOf: [getNotSchema()], });
- notSchema.js
import { getFilterSchema } from "./filterSchema.js"; export const getNotSchema = () => ({ type: "object", properties: { $not: getFilterSchema(), }, required: ["$not"], additionalProperties: false, });
- main.js调用
import { getFilterSchema } from "./filterSchemas/filterSchema.js"; // 调用函数生成Schema const filterSchema = getFilterSchema(); console.log(filterSchema);
内容的提问来源于stack exchange,提问作者baitendbidz
相关产品推荐
相关产品推荐

