登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go jsonschema 没写 schema 声明时怎样选择草案

来源:17golang原创

时间:2026-10-30 16:19:29 193浏览 收藏

我第一次把一组历史 JSON Schema 接到 Go 校验服务时,最容易忽略的不是校验调用,而是 schema 文件里没有写 $schema。这时到底按 Draft 7 还是 Draft 2020-12 解释,并不是凭文件名猜,而是由 jsonschema.Compiler 的配置决定。

使用 github.com/santhosh-tekuri/jsonschema/v5 时,Compiler.Draft 就是缺少 $schema 时的回退选择;如果编译器也不设置它,文档当前说明会采用支持范围内的最新草案,也就是 Draft 2020-12。项目要保持跨服务、跨版本的一致结果,最稳妥的做法是显式固定草案,并把它写进配置和回归样例。

官方地址:https://pkg.go.dev/github.com/santhosh-tekuri/jsonschema/v5

缺少 $schema 时,真正起作用的是 Compiler.Draft

这个包支持 Draft 4、Draft 6、Draft 7、Draft 2019-09 和 Draft 2020-12。schema 没有声明方言时,编译器不能从 JSON 内容可靠推断版本,于是使用 Compiler.Draft。如果这个字段保持默认值,当前 v5 文档给出的默认方向是最新支持草案。

下面这段代码把草案固定为 Draft 7。示例重点是配置关系:先创建编译器,再设置 Draft,随后注册没有 $schema 的 schema,最后编译。

Go jsonschema 缺少 schema 声明时由 Compiler Draft 选择草案的结构图
图1:缺少 $schema 时的草案选择关系说明图,不是运行截图或执行证据。
package main

import (
    "fmt"
    "strings"

    "github.com/santhosh-tekuri/jsonschema/v5"
)

func main() {
    compiler := jsonschema.NewCompiler()
    // schema 没有 $schema 时,统一按 Draft 7 编译,避免依赖库默认值。
    compiler.Draft = jsonschema.Draft7

    schemaText := `{
        "type": "object",
        "properties": {"name": {"type": "string"}},
        "required": ["name"]
    }`
    // 把内存中的 schema 注册为 profile.json,名称会用于后续 Compile。
    if err := compiler.AddResource("profile.json", strings.NewReader(schemaText)); err != nil {
        panic(fmt.Errorf("添加 schema 失败: %w", err))
    }
    // Compile 只负责得到可复用的 Schema,实例校验可在后续调用 Validate。
    schema, err := compiler.Compile("profile.json")
    if err != nil {
        panic(fmt.Errorf("编译 schema 失败: %w", err))
    }
    fmt.Println(schema != nil)
}

这里的关键不是把 Draft7 当成永远正确的答案,而是不要让草案选择隐藏在默认值里。若上游 schema 按 Draft 2020-12 编写,就应该固定为 jsonschema.Draft2020;如果它来自旧服务并长期按 Draft 4 运行,则要保留旧方言,先做兼容回归再迁移。

三种选择方式,迁移时不要混在一起

schema 状态编译器配置实际判断适合场景
包含 $schema可设置,也可不设置优先按 schema 自己声明的草案解释外部标准 schema 或供应商 schema
不包含 $schema设置 Compiler.Draft使用该字段作为明确回退兼容历史文件、固定服务契约
不包含 $schema不设置 Compiler.Draft依赖当前支持的默认最新草案临时试验,不适合长期契约

我在迁移旧 schema 时,最先改的通常不是正文结构,而是把“没有声明”登记成一个明确决定:这个目录里的文件全部按 Draft 7,还是让每个文件补上对应的 $schema。两种做法都可以,最怕同一服务里一部分依赖默认值、一部分又在代码中偷偷固定版本。

Go jsonschema 显式 schema 声明与 Compiler Draft 配置的对比结构图
图2:显式声明与编译器回退配置的对比说明图,不是第三方界面截图。

怎样选择 Draft4、Draft7 或 Draft2020

选择草案时,先看 schema 的来源和已有消费者,再看目标库是否使用了特定版本的关键字。可以按下面的顺序收敛:

  • 需要兼容旧系统:先保留旧系统正在使用的草案,例如 Draft 4,不要只因为新版本可用就直接切换。
  • 内部服务长期维护:如果历史文件主要按 Draft 7 约定编写,可以把 compiler.Draft = jsonschema.Draft7 作为服务级固定配置。
  • 新建并明确采用新规范:使用 jsonschema.Draft2020,同时在 schema 或契约文档中写明方言,避免其他语言实现误读。

“最新”不等于“最兼容”。草案切换可能影响关键字的解释、引用方式以及外部校验器的行为;版本迁移应以同一批样例在旧、新配置下的结果对比为依据,而不是只看编译是否成功。

把隐式默认值改成可回归的配置

如果暂时不能修改历史 schema,可以先在统一入口固定草案。下面的辅助函数把草案作为参数传入,调用方可以按服务契约选择,而不是让每个业务函数各自创建默认编译器。

func compileSchema(name string, schemaText string, draft *jsonschema.Draft) (*jsonschema.Schema, error) {
    compiler := jsonschema.NewCompiler()
    // 把草案选择放到调用方可见的位置,便于按契约切换并测试。
    compiler.Draft = draft
    // 注册资源后再编译,name 也会成为错误信息中的定位线索。
    if err := compiler.AddResource(name, strings.NewReader(schemaText)); err != nil {
        return nil, fmt.Errorf("注册 %s 失败: %w", name, err)
    }
    // 返回可复用的 Schema;调用方可重复 Validate,不必重复编译。
    compiled, err := compiler.Compile(name)
    if err != nil {
        return nil, fmt.Errorf("编译 %s 失败: %w", name, err)
    }
    return compiled, nil
}

// 旧契约按 Draft 7 解释,新契约可单独传入 Draft2020。
schema, err := compileSchema("profile.json", schemaText, jsonschema.Draft7)
if err != nil {
    return err
}
_ = schema

实际项目中还应把以下信息放进测试或配置评审记录:schema 文件是否包含 $schema、期望的草案常量、代表性实例、旧配置与新配置的校验差异。这样升级依赖时,改变默认行为会先在代码评审里暴露,而不是上线后才发现某个实例突然不通过。

常见疑问

Compiler.Draft 设置后,会覆盖 schema 自己的 $schema 吗?

它的职责是“schema 缺少 $schema 时使用哪个草案”。对于已经写明声明的 schema,应以 schema 自己的方言为准;迁移时不要同时写一个互相矛盾的编译器默认值和文件声明。

不写 $schema,直接使用默认值可以吗?

可以编译,但默认值会随库支持范围变化,不适合作为长期跨服务契约。只要 schema 会被多个服务、多个语言实现共享,就建议补声明或显式设置 Compiler.Draft。

所有项目都应该统一改成 Draft2020 吗?

不应该一刀切。先确认现有 schema、上下游校验器和关键字使用情况,再决定是否迁移;没有回归样例时,保留原草案并显式配置通常更安全。

迁移清单

  1. 盘点没有 $schema 的文件,并记录当前服务实际依赖的草案。
  2. 在统一编译入口显式设置 Compiler.Draft,不要在业务分支里重复创建默认编译器。
  3. 为代表性实例保留通过、失败和引用场景,比较固定草案前后的结果。
  4. 条件允许时给共享 schema 补上明确的 $schema 声明,并同步契约文档。
  5. 升级 jsonschema 依赖后重新执行这组回归样例,确认默认行为没有悄悄变成业务规则。

一句话总结:Go jsonschema/v5 没写 $schema 时,先看 Compiler.Draft;未设置时会落到当前支持的最新草案。想要可预测的兼容性,就把草案选择写进 schema 或编译器配置,而不是把它交给默认值。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>