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

使用AJV验证JSON Schema Draft2020-12时出现重复Schema错误求助

解决AJV重复注册JSON Schema Draft 2020-12的错误

问题根源

你遇到的schema with key or id "https://json-schema.org/draft/2020-12/schema" already exists错误,本质是:

  • 多个组件中重复创建AJV实例,且多次尝试编译/注册同一个带$id的Draft 2020-12 schema
  • Output组件中每次点击"Run Code"都重新编译schema,导致AJV重复注册同一ID的schema

解决方案

1. 统一管理AJV实例与Schema编译

创建全局单例的AJV实例,只编译一次Draft 2020-12 schema,避免重复注册。

步骤1:创建AJV工具模块

在项目中新建utils/ajv.ts文件,集中管理AJV实例和预编译的验证函数:

import Ajv from "ajv/dist/2020";
import * as draft2020Schema from "~/_includes/draft/2020-12/schema.json";

// 创建全局唯一的AJV实例
const ajv = new Ajv({ strict: false, allErrors: true });

// 预编译并缓存Draft 2020-12的验证函数(只执行一次)
export const validateDraft2020Schema = ajv.compile(draft2020Schema);

2. 修改Output组件

移除组件内部的AJV实例创建和重复编译逻辑,直接使用预编译的验证函数:

import React, { useState } from "react";
import { validateDraft2020Schema } from "~/utils/ajv";

interface OutputProps {
  editorRef: React.RefObject<any>;
  language: string;
}

const Output: React.FC<OutputProps> = ({ editorRef, language }) => {
  const [output, setOutput] = useState<string[] | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [isError, setIsError] = useState<boolean>(false);

  const runCode = async () => {
    const sourceCode = editorRef.current?.getValue();
    setIsLoading(true);
    setIsError(false);
    setOutput(null);

    if (!sourceCode) {
      setOutput(["请输入JSON代码"]);
      setIsLoading(false);
      return;
    }

    try {
      // 先解析用户输入的JSON(处理非法JSON的情况)
      const userSchema = JSON.parse(sourceCode);
      // 使用预编译的验证函数
      const valid = validateDraft2020Schema(userSchema);

      if (!valid) {
        // 格式化错误信息,方便用户查看
        const errorMessages = validateDraft2020Schema.errors?.map(err => 
          `${err.instancePath}: ${err.message}`
        ) || ["无效的JSON Schema"];
        throw new Error(`JSON Schema验证失败:\n${errorMessages.join("\n")}`);
      } else {
        setOutput(["JSON Schema符合Draft 2020-12规范"]);
      }
    } catch (error: any) {
      console.error("验证错误:", error);
      setOutput([error.message]);
      setIsError(true);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div style={{ width: "50%" }}>
      <h2 style={{ fontSize: "1.5rem", marginBottom: "0.5rem" }}>Output</h2>
      <button
        style={{
          padding: "0.5rem 1rem",
          fontSize: "1rem",
          marginBottom: "1rem",
          backgroundColor: isLoading ? "#ccc" : "#4caf50",
          color: "#fff",
          border: "none",
          borderRadius: "4px",
          cursor: "pointer",
        }}
        disabled={isLoading}
        onClick={runCode}
      >
        {isLoading ? "Running..." : "Run Code"}
      </button>
      <div
        style={{
          height: "75vh",
          padding: "0.5rem",
          border: `1px solid ${isError ? "red" : "#333"}`,
          borderRadius: "9px",
          overflowY: "auto",
          backgroundColor: isError ? "#ffcdd2" : "#f5f5f5",
        }}
      >
        {output
          ? output.map((line, i) => <p key={i}>{line}</p>)
          : 'Click "Run Code" to see the output here'}
      </div>
    </div>
  );
};

export default Output;

3. 清理CodeEditor组件

移除组件内无用的AJV相关代码:

import React, { useRef, useState } from "react";
import { Editor } from "@monaco-editor/react";
import LanguageSelector from "./LanguageSelector";
import Output from "./Output";
import { LANGUAGE_VERSIONS, CODE_SNIPPETS } from "~/constants/languages";

const CodeEditor: React.FC = () => {
  const editorRef = useRef<any>(null);
  const [value, setValue] = useState<string>("");
  const [language, setLanguage] = useState<string>("json");

  const onMount = (editor: any) => {
    editorRef.current = editor;
    editor.focus();
  };

  const onSelect = (language: string) => {
    setLanguage(language);
    setValue(CODE_SNIPPETS[language]);
  };

  return (
    <div className="flex w-full">
      <div className="w-1/2">
        <Editor
          options={{
            minimap: {
              enabled: false,
            },
          }}
          height="75vh"
          width="80%"
          theme="vs-dark"
          language={language}
          defaultValue={CODE_SNIPPETS[language]}
          onMount={onMount}
          value={value}
          onChange={(value) => setValue(value || '')}
        />
      </div>
      <Output editorRef={editorRef} language={language} />
      <div><LanguageSelector language={language} onSelect={onSelect}/></div>
    </div>
  );
};

export default CodeEditor;

额外注意事项

  • 确保你的Draft 2020-12 schema文件schema.json中的$id字段是https://json-schema.org/draft/2020-12/schema,没有重复定义
  • 如果需要支持其他schema版本,可在AJV工具模块中扩展预编译的验证函数
  • 若仍有重复注册问题,可在AJV实例创建时添加skipFetchingMissingSchemas: true参数,避免自动拉取外部schema

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 15:48:13