MongoDB的$jsonSchema校验必须显式声明required字段,否则缺失字段仍能插入;嵌套对象需逐层展开校验,不递归生效;混合类型应拆分为专用字段而非依赖动态$type;validator仅作兜底,强类型保障须结合Zod等应用层工具。

验证规则必须显式声明 required 字段
不写 required,字段就不是必填的——哪怕你在 properties 里写了类型校验,缺失该字段的文档照样能插入成功。这是最常被忽略的一点。
常见错误现象:定义了 { name: { bsonType: "string" } },结果插入 { age: 25 } 也能过;实际业务中这会导致下游解析失败或空指针。
-
required是数组,必须列出所有真正强制存在的字段名,比如["name", "email"] - 如果字段允许为空(
null),需在bsonType中显式包含"null",例如bsonType: ["string", "null"] -
_id字段默认存在,若使用additionalProperties: false,必须把它加进properties并设为bsonType: "objectId",否则所有插入都会失败
嵌套对象要逐层展开写校验
MongoDB 的 $jsonSchema 不递归生效。你给 config 字段设 bsonType: "object",只校验它是不是对象,里面字段完全不管——这和前端用 Zod 做嵌套校验的直觉完全不同。
使用场景:配置项如 { timeout: 5000, retry: { max: 3, delay: "exponential" } },必须把 retry.max 和 retry.delay 单独写进 properties 的嵌套结构里。
- 写法示例:
properties: { config: { bsonType: "object", properties: { timeout: { bsonType: "int" }, retry: { bsonType: "object", properties: { max: { bsonType: "int", minimum: 1 } } } } } } - 漏一层(比如只校验到
retry,没进到retry.max)等于没校验 - 数组内元素校验要用
items,且items是数组(支持多类型)或单个 schema 对象,不能直接写bsonType
混合类型字段不能靠 $type 动态判断
想让一个 value 字段既能存 string 又能存 number,还要求类型和 value_kind 字段匹配?MongoDB 不支持在 $jsonSchema 里写条件表达式,$cond、$switch 在 validator 里无效。
真实踩坑点:有人试图写 { value: { $type: { $cond: [...] } } },mongosh 直接报语法错误;或者只用 bsonType: ["string", "int", "double"],结果插入 { value: null } 也通过,但应用层解析崩了。
- 安全做法是拆字段:
value_string、value_number、value_boolean+value_type枚举,再用required和互斥逻辑约束 - 若坚持单字段,至少补上
"null"到bsonType数组,并接受查询性能损失——find({ value: 42 })可能全表扫描 - 别指望数据库替你做类型归一化;校验只是兜底,真正的类型保障得靠应用层解析(如 Zod 的
z.union([z.string(), z.number()]))
validator 不等于应用层类型系统
它只在写入时触发一次,且只认 BSON 类型名("string"、"int"、"objectId"),不认 JS/TS 的 number 或 any。更关键的是:它拦不住语义错误。
比如 retry_count: -1 过得了 bsonType: "int",但业务上绝不允许负数;url: "foo" 过得了 pattern: ".+",但根本不是合法 URL。
- validator 是最后一道物理防线,不是替代应用层校验的方案
- 正则校验写
pattern时注意:MongoDB 使用 PCRE,不支持/(? 这类 JS 有的高级语法 - 数值范围用
minimum/maximum,但注意int和double的取值边界不同,Int32(2147483647)和Double(9007199254740992)都算合法
真正麻烦的从来不是怎么写 schema,而是怎么让开发、测试、运维对“这个字段到底允不允许 null”“created_at 是 Date 还是 ISOString”达成一致——validator 只会按字面执行,不会替你读心。











