使用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
相关产品推荐
相关产品推荐

