JSON Schema 2020-12 validator with a zod-style builder API for MoonBit — one engine, two frontends (ajv / zod), WASM-first.
本项目为 2026 MoonBit 国产基础软件开源大赛 参赛项目。
moon add QuietlyChan/moonschema// moon.pkg: import "QuietlyChan/moonschema" @moonschema
let schema_text =
#|{
#| "type": "object",
#| "required": ["id", "email"],
#| "properties": {
#| "id": { "type": "integer", "minimum": 1 },
#| "email": { "type": "string" }
#| },
#| "additionalProperties": false
#|}
let v = @moonschema.compile(@moonschema.parse(schema_text))
let req =
#|{ "id": -3, "email": 42, "extra": true }
let ok = v.check(@moonschema.parse(req)) // false
println(@moonschema.validate_to_string(v, @moonschema.parse(req)))
// /id: must be >= 1 [minimum @ #/properties/id/minimum]
// /email: must be string [type @ #/properties/email/type]
// (root): must NOT have additional properties ("extra") [additionalProperties @ #/additionalProperties]// moon.pkg: import "QuietlyChan/moonschema/builder" @builder
let user = @builder.object({
"name": @builder.string().min_len(1).max_len(50), // 默认必填,与 zod 一致
"age": @builder.integer().min(0).max(150).optional(),
"email": @builder.string().email(), // builder 层默认断言 format
}).strict() // 拒绝未声明字段
let v = user.compile() // moonschema 校验器
let doc = user.to_schema() // 标准 JSON Schema 2020-12 文档,可与任何语言互通let ok : Bool = v.check(instance) // 只要结论
let errors : Array[@moonschema.ValidationError] = v.validate(instance)
for e in errors {
println("\{e.instance_path}: \{e.message}") // 结构化消费
}bash playground/build.sh # moon build --target js --release --strip + 拷贝产物
cd playground/web && python -m http.server 8080 # 或 npx serve .
# 打开 http://localhost:8080| 导出函数 | 用途 |
|---|---|
| validate_json(schemaText, dataText) | 一次调用完成解析→编译→校验,返回结构化 JSON 结果 |
| compile_schema(schemaText) -> handle | ajv 式编译一次(句柄,失败 -1) |
| check_with(handle, dataText) -> bool | 热路径布尔判定 |
| validate_with(handle, dataText) -> string | 校验并返回完整错误 JSON |
关于 wasm-gc:引擎已验证可在 wasm-gc 目标编译并导出数值函数(link.wasm-gc.exports,Node 24 WebAssembly.instantiate 实测通过);但字符串在 wasm-gc 边界是 GC 对象,对 JS 不透明,需 JS-string-builtins 方案——Playground 因此选择字符串原生互通的 JS 后端。
| 实现 / 工作负载 | ops/s | µs per validate |
|---|---|---|
| ajv valid (预解析对象) | 3,066,168 | 0.33 |
| zod valid (预解析对象) | 456,988 | 2.19 |
| ajv valid (+JSON.parse) | 400,898 | 2.49 |
| moonschema valid (字符串入口) | 89,177 | 11.21 |
| zod invalid (预解析对象) | 155,439 | 6.43 |
| moonschema invalid (字符串+错误报告) | 61,078 | 16.37 |
| 类别 | 关键词 |
|---|---|
| 核心 | type(含数组形式)、enum、const、$ref(本地 JSON Pointer,支持递归)、$defs、布尔模式 true/false |
| 数值 | minimum、maximum、exclusiveMinimum、exclusiveMaximum、multipleOf(浮点容差判定) |
| 字符串 | minLength、maxLength(按 Unicode 码点计数)、pattern(基于 core 正则引擎)、format(默认 annotation;assert_format 开启后断言 email / uuid / ipv4) |
| 数组 | items、prefixItems、minItems、maxItems、uniqueItems、contains、minContains、maxContains |
| 对象 | properties、patternProperties、required、additionalProperties、propertyNames、minProperties、maxProperties、dependentRequired、dependentSchemas |
| 组合 | allOf、anyOf、oneOf、not、if/then/else |
| 扩展 | x-rules:跨字段动态规则 DSL(见下节) |
| 编译选项 | strict(未知关键词报错,x- 前缀扩展放行)、assert_format、locale(错误消息 EN/ZH) |
// builder 侧
let order = @builder.object({
"start": @builder.string(),
"end": @builder.string(),
"total": @builder.number(),
"items": @builder.array(@builder.object({
"price": @builder.number(), "qty": @builder.number(),
}).optional()),
}).satisfy("end > start")
.satisfy("items[0].price * items[0].qty == total")
// JSON Schema 侧等价写法
// { "x-rules": ["end > start", "items[0].price * items[0].qty == total"] }| 类别 | 运算符 |
|---|---|
| 比较 | == != < <= > >=(数值按大小、字符串按字典序——ISO 日期可直接比较;跨类型为假) |
| 算术 | + - * / %(除零等产生非有限值时规则不可满足) |
| 逻辑 | && \|\| !(短路) |
| 字面量 | 数字、字符串("...")、true / false / null |
| 路径 | 字段名、点号嵌套(address.city)、数组下标(items[0].price) |
// JSON Schema 侧
let v = compile(schema, options=CompileOptions::new(locale=ZH))
// builder 侧
let v = user.compile(locale=ZH)(root): 缺少必需属性 "name" [required @ #/required]
/age: 必须 >= 0 [minimum @ #/properties/age/minimum]
(root): 规则 "age > 0" 未满足 [x-rules @ #/x-rules/1]groups: 384 (compile-rejected: 133)
pass: 974 fail: 1 skip: 326
pass rate (of judged 975): 99.90%| moonschema | ajv | zod |
|---|---|---|
| compile(schemaDoc) | ajv.compile(schema) | — |
| v.validate(x) + errors 数组 | validate(x) + ajv.errors | schema.safeParse(x) |
| v.check(x) | validate(x) 返回值 | schema.check(x) |
| @builder.object({...}) | — | z.object({...}) |
| .optional() / .nullable() | — | .optional() / .nullable() |
| .strict() / .catchall(s) | additionalProperties: false | .strict() / .catchall() |
| .enum_(vals) / .literal(v) | enum / const | z.enum / z.literal |
| .email() / .uuid() / .ipv4() | format: "email" + ajv-formats | .email() / .uuid() |
moon check # 静态检查
moon test # 运行测试(native)
moon test --target wasm-gc # 运行测试(WASM-GC)
moon run cmd/demo # 可运行示例
moon fmt && moon info # 格式化 + 更新包接口let v = compile(
parse!(#|{ "type": "object", "required": ["id"] }#|),
)
v.check(parse!(#|{"id": 1}#|)) // trueInstall
Download zipJSON Schema 2020-12 validator with a zod-style builder API for MoonBit — one engine, two frontends (ajv / zod), WASM-first.