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

如何初始化互相引用的变量?解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 23:18:32