news 2026/9/10 13:40:19

Mongoose SchemaTypes 完全指南:路径类型定义、SchemaType 选项与类型转换实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mongoose SchemaTypes 完全指南:路径类型定义、SchemaType 选项与类型转换实战

Mongoose SchemaTypes 完全指南:路径类型定义、SchemaType 选项与类型转换实战

【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongoose

Mongoose 是运行在异步环境下的 MongoDB 对象建模工具,而 SchemaTypes 正是其数据建模的基石:它为模型中的每一个属性(path)定义类型、默认值、校验规则、getter/setter 与字段选择行为。本文以官方 SchemaTypes 文档 为核心骨架,结合本仓库源码实现,系统讲解什么是 SchemaType、type关键字的特殊语义、全部内置 SchemaType 的配置选项与转换规则,并给出可直接复制运行的实战示例,帮助你彻底掌握 Mongoose 路径类型体系。

什么是 SchemaType?

可以把 Mongoose 的 schema 理解为 Mongoose model 的"配置对象",而 SchemaType 则是单个属性的配置对象。一个 SchemaType 决定了某个 path 应该是什么类型、是否有 getter/setter,以及哪些值对该 path 合法。

const schema = new Schema({ name: String }); schema.path('name') instanceof mongoose.SchemaType; // true schema.path('name') instanceof mongoose.Schema.Types.String; // true schema.path('name').instance; // 'String'

关键要区分SchemaType 与类型本身mongoose.ObjectId !== mongoose.Types.ObjectId。SchemaType 只是给 Mongoose 使用的配置对象,mongoose.ObjectId这个 SchemaType 的实例并不会真正创建 MongoDB ObjectId,它只是 schema 中某个 path 的配置。

从源码看,所有内置 SchemaType 都注册在 lib/schema/index.js 中,它们统一继承自 lib/schemaType.js 这个基类,例如 lib/schema/string.js 中的SchemaString就是通过SchemaType.call(this, key, options, 'String', parentSchema)完成初始化,并带有自己的schemaNamedefaultOptionsOptionsConstructor。因此,Mongoose 插件也可以注册自定义 SchemaType 来扩展这个体系。

SchemaTypes 全集

Mongoose 内置的全部合法 SchemaType 如下(插件还可以添加自定义类型,如 int32、mongoose-long 等,可通过 Mongoose 插件站点搜索获取):

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • Union
  • ObjectId
  • Array
  • Decimal128
  • Map
  • Schema
  • UUID
  • BigInt
  • Double
  • Int32

综合示例

const schema = new Schema({ name: String, binary: Buffer, living: Boolean, updated: { type: Date, default: Date.now }, age: { type: Number, min: 18, max: 65 }, mixed: Schema.Types.Mixed, union: { type: Schema.Types.Union, of: [String, Number] }, _someId: Schema.Types.ObjectId, decimal: Schema.Types.Decimal128, double: Schema.Types.Double, int32bit: Schema.Types.Int32, array: [], ofString: [String], ofNumber: [Number], ofDates: [Date], ofBuffer: [Buffer], ofBoolean: [Boolean], ofMixed: [Schema.Types.Mixed], ofObjectId: [Schema.Types.ObjectId], ofArrays: [[]], ofArrayOfNumbers: [[Number]], nested: { stuff: { type: String, lowercase: true, trim: true } }, map: Map, mapOfString: { type: Map, of: String } }); // example use const Thing = mongoose.model('Thing', schema); const m = new Thing; m.name = 'Statue of Liberty'; m.age = 125; m.updated = new Date; m.binary = Buffer.alloc(0); m.living = false; m.mixed = { any: { thing: 'i want' } }; m.markModified('mixed'); m._someId = new mongoose.Types.ObjectId; m.array.push(1); m.ofString.push('strings!'); m.ofNumber.unshift(1, 2, 3, 4); m.ofDates.addToSet(new Date); m.ofBuffer.pop(); m.ofMixed = [1, [], 'three', { four: 5 }]; m.nested.stuff = 'good'; m.map = new Map([['key', 'value']]); m.save(callback);

type关键字的特殊语义

type是 Mongoose schema 中的特殊属性。当 Mongoose 在 schema 中发现名为type的嵌套属性时,会假定你需要用给定类型定义一个 SchemaType:

// 3 个 String SchemaTypes: 'name', 'nested.firstName', 'nested.lastName' const schema = new Schema({ name: { type: String }, nested: { firstName: { type: String }, lastName: { type: String } } });

因此,如果你真的想定义一个名为type的属性,就需要多做一些工作。例如构建一个股票持仓应用,想存储资产的type(股票 stock、债券 bond、ETF 等),直观写法可能是:

const holdingSchema = new Schema({ // 你期望 `asset` 是一个拥有 2 个属性的对象, // 但不幸的是 `type` 在 Mongoose 中是特殊的, // 所以 Mongoose 会把该 schema 解释为 `asset` 是一个字符串 asset: { type: String, ticker: String } });

当 Mongoose 看到type: String时,会假定你意思是asset应该是字符串,而不是一个带type属性的对象。正确的定义方式如下:

const holdingSchema = new Schema({ asset: { // 变通方法:确保 Mongoose 知道 `asset` 是对象、 // `asset.type` 是字符串,而不是把 `asset` 当作字符串 type: { type: String }, ticker: String } });

更多细节可参考仓库 FAQ 文档 中关于type关键字的说明。

SchemaType Options(SchemaType 选项)

你可以直接用类型本身声明 schema type,也可以用带type属性的对象声明:

const schema1 = new Schema({ test: String // `test` 是一个 String 类型的 path }); const schema2 = new Schema({ // `test` 对象包含 "SchemaType options" test: { type: String } // `test` 是一个 string 类型的 path });

除了type属性,你还可以为 path 指定附加属性。例如想在保存前把小写化字符串:

const schema2 = new Schema({ test: { type: String, lowercase: true // 总是把 `test` 转换成小写 } });

你可以向 SchemaType options 中添加任何自定义属性,很多插件依赖自定义的 SchemaType options(例如 mongoose-autopopulate 插件在 options 中设置autopopulate: true即可自动 populate 路径)。Mongoose 内置支持若干 SchemaType 选项,如上面示例中的lowercaselowercase只对字符串生效;有些选项对所有 schema type 通用,有些则只对特定类型生效。

所有 SchemaType 通用选项

  • required: boolean 或 function,若为 true 则为该属性添加 required 校验器
  • default: 任意值或 function,为该 path 设置默认值;如果值是函数,则使用函数返回值作为默认值
  • select: boolean,指定查询的默认投影(projection)行为
  • validate: function,为该属性添加校验函数
  • get: function,使用Object.defineProperty()为该属性定义自定义 getter
  • set: function,使用Object.defineProperty()为该属性定义自定义 setter
  • alias: string(mongoose >= 4.10.0),定义一个虚拟属性(virtual),用来 get/set 该 path
  • immutable: boolean,将 path 定义为不可变;除非父文档isNew: true,否则 Mongoose 禁止修改 immutable path
  • transform: function,调用Document#toJSON()(包括对文档执行JSON.stringify())时触发
const numberSchema = new Schema({ integerOnly: { type: Number, get: v => Math.round(v), set: v => Math.round(v), alias: 'i' } }); const Number = mongoose.model('Number', numberSchema); const doc = new Number(); doc.integerOnly = 2.001; doc.integerOnly; // 2 doc.i; // 2 doc.i = 3.001; doc.integerOnly; // 3 doc.i; // 3

注意immutable的底层实现:仓库 lib/helpers/query/handleImmutable.js 与 lib/helpers/schematype/handleImmutable.js 会在更新操作(update 与文档保存)期间把 immutable path 从修改集合中剥离,从而防止被意外改写,只有新建文档(isNew: true)时允许写入。

索引选项

你还可以用 schema type options 定义 MongoDB 索引:

  • index: boolean,是否在该属性上定义索引
  • unique: boolean,是否定义唯一索引
  • sparse: boolean,是否定义稀疏索引
const schema2 = new Schema({ test: { type: String, index: true, unique: true // 唯一索引。如果指定 `unique: true` // 再指定 `index: true` 是可选的 } });

各类型专属选项

String(详见 validation 文档):

  • lowercase: boolean,是否总是对值调用.toLowerCase()
  • uppercase: boolean,是否总是对值调用.toUpperCase()
  • trim: boolean,是否总是对值调用.trim()
  • match: RegExp,创建校验器,检查值是否匹配给定的正则表达式
  • enum: Array,创建校验器,检查值是否在给定数组中
  • minLength: Number,创建校验器,检查值长度不小于给定值
  • maxLength: Number,创建校验器,检查值长度不大于给定值
  • populate: Object,设置默认 populate 选项

Number

  • min: Number,创建校验器,检查值大于等于给定最小值
  • max: Number,创建校验器,检查值小于等于给定最大值
  • enum: Array,创建校验器,检查值与数组中某个值严格相等
  • populate: Object,设置默认 populate 选项

Date

  • min: Date,创建校验器,检查值大于等于给定最小值
  • max: Date,创建校验器,检查值小于等于给定最大值
  • expires: Number 或 String,创建 TTL 索引,值以秒为单位

ObjectId

  • populate: Object,设置默认 populate 选项

各 SchemaType 实战指南

String

声明字符串 path,既可以使用String全局构造函数,也可以使用字符串'String'

const schema1 = new Schema({ name: String }); // name 会被 cast 为字符串 const schema2 = new Schema({ name: 'String' }); // 等价 const Person = mongoose.model('Person', schema2);

如果传入的元素有toString()函数,Mongoose 会调用它——除非该元素是数组,或者toString()函数与Object.prototype.toString()严格相等:

new Person({ name: 42 }).name; // "42" 作为字符串 new Person({ name: { toString: () => 42 } }).name; // "42" 作为字符串 // "undefined",如果 save() 该文档会得到 cast 错误 new Person({ name: { foo: 42 } }).name;

从 lib/schema/string.js 的源码可以看到,SchemaString维护了enumValuesregExp两个实例属性(对应enummatch选项),并支持通过SchemaString.cast(caster)静态方法整体替换 cast 函数,或传入false禁用 cast(仅允许null/undefined和非对象值)。

Number

声明数字 path,可以使用Number全局构造函数或字符串'Number'

const schema1 = new Schema({ age: Number }); // age 会被 cast 为 Number const schema2 = new Schema({ age: 'Number' }); // 等价 const Car = mongoose.model('Car', schema2);

下面这些值都能成功 cast 为 Number:

new Car({ age: '15' }).age; // 15 作为 Number new Car({ age: true }).age; // 1 作为 Number new Car({ age: false }).age; // 0 作为 Number new Car({ age: { valueOf: () => 83 } }).age; // 83 作为 Number

如果传入的对象带有返回 Number 的valueOf()函数,Mongoose 会调用它并把返回值赋给该 path。nullundefined不会被 cast。NaN、能 cast 成 NaN 的字符串、数组,以及没有valueOf()函数的对象,都只会在验证阶段抛出 CastError——即初始化时不抛错,只有验证时才抛错。

Dates

内置的 Date 方法 并没有被接入 Mongoose 的变更追踪逻辑。也就是说,如果你在文档里用setMonth()之类的方法修改了 Date,Mongoose 不会感知到这次改动,doc.save()也就不会持久化该修改。如果必须用内置方法修改 Date 类型,请在保存前调用doc.markModified('pathToYourDate')告知 Mongoose:

const Assignment = mongoose.model('Assignment', { dueDate: Date }); const doc = await Assignment.findOne(); doc.dueDate.setMonth(3); await doc.save(); // 这不会保存你的修改! doc.markModified('dueDate'); await doc.save(); // 生效

Buffer

声明 Buffer path,可以使用Buffer全局构造函数或字符串'Buffer'

const schema1 = new Schema({ binData: Buffer }); // binData 会被 cast 为 Buffer const schema2 = new Schema({ binData: 'Buffer' }); // 等价 const Data = mongoose.model('Data', schema2);

Mongoose 可以成功地把下面的值 cast 成 buffer:

const file1 = new Data({ binData: 'test'}); // {"type":"Buffer","data":[116,101,115,116]} const file2 = new Data({ binData: 72987 }); // {"type":"Buffer","data":[27]} const file4 = new Data({ binData: { type: 'Buffer', data: [1, 2, 3]}}); // {"type":"Buffer","data":[1,2,3]}

Mixed

Mixed 是"什么都可以"的 SchemaType。Mongoose 不会对 Mixed path 做任何 cast。可以用Schema.Types.Mixed或空对象字面量来定义 Mixed path,下面几种写法等价:

const Any = new Schema({ any: {} }); const Any = new Schema({ any: Object }); const Any = new Schema({ any: Schema.Types.Mixed }); const Any = new Schema({ any: mongoose.Mixed });

由于 Mixed 是无 schema 的类型,你可以随意把值改成任何内容,但 Mongoose 会失去自动检测并保存这些修改的能力。要告诉 Mongoose Mixed 类型的值发生了变化,需要调用doc.markModified(path),并传入刚改过的 Mixed 类型的 path:

person.anything = { x: [3, 4, { y: 'changed' }] }; person.markModified('anything'); person.save(); // Mongoose 会保存对 `anything` 的修改。

为避免这些副作用,也可以改用 Subdocument path。

ObjectIds

ObjectId 是通常用于唯一标识符的特殊类型。下面声明一个driver为 ObjectId 的 schema:

const mongoose = require('mongoose'); const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId });

ObjectId是一个类,ObjectId 是对象,但通常被表示为字符串。用toString()把 ObjectId 转成字符串时,会得到 24 位十六进制字符串:

const Car = mongoose.model('Car', carSchema); const car = new Car(); car.driver = new mongoose.Types.ObjectId(); typeof car.driver; // 'object' car.driver instanceof mongoose.Types.ObjectId; // true car.driver.toString(); // 类似 "5e1a0651741b255ddda996c4"

Boolean

Mongoose 中的 Boolean 是原生 JavaScript 布尔值。默认情况下,Mongoose 把下面的值 cast 为true

  • true
  • 'true'
  • 1
  • '1'
  • 'yes'

把下面的值 cast 为false

  • false
  • 'false'
  • 0
  • '0'
  • 'no'

任何其他值都会导致 CastError。你可以通过convertToTrueconvertToFalse属性修改 Mongoose 转成 true/false 的值集合,这两个属性都是 JavaScript Set:

const M = mongoose.model('Test', new Schema({ b: Boolean })); console.log(new M({ b: 'nay' }).b); // undefined // Set { false, 'false', 0, '0', 'no' } console.log(mongoose.Schema.Types.Boolean.convertToFalse); mongoose.Schema.Types.Boolean.convertToFalse.add('nay'); console.log(new M({ b: 'nay' }).b); // false

从源码看,默认转换集合定义在 lib/cast/boolean.js 中:convertToTrue = new Set([true, 'true', 1, '1', 'yes'])convertToFalse = new Set([false, 'false', 0, '0', 'no']);lib/schema/boolean.js 通过Object.defineProperty把这两个集合暴露为SchemaBoolean的静态属性,并额外提供了SchemaBoolean.cast(caster)静态方法,可整体替换 cast 函数(传false则退回严格模式,只接受原生 boolean)。仓库的 test/schema.boolean.test.js 有大量针对这些 cast 行为与convertToTrue/convertToFalse扩展的测试用例。

Arrays

Mongoose 支持 SchemaType 数组和子文档(subdocument)数组。SchemaType 数组也叫primitive arrays(基础类型数组),子文档数组也叫document arrays(文档数组)

const ToySchema = new Schema({ name: String }); const ToyBoxSchema = new Schema({ toys: [ToySchema], buffers: [Buffer], strings: [String], numbers: [Number] // ... 等等 });

数组是特殊的,因为它们隐式地有一个默认值[](空数组):

const ToyBox = mongoose.model('ToyBox', ToyBoxSchema); console.log((new ToyBox()).toys); // []

要覆盖这个默认值,需要把默认值设为undefined

const ToyBoxSchema = new Schema({ toys: { type: [ToySchema], default: undefined } });

注意default应用在它声明的层级上。上面的例子中default紧挨着type: [ToySchema],所以它是数组的默认值。如果把它放进方括号内,它就成了数组中每个元素的默认值,而数组仍保留隐式的[]默认值:

const ArrayDefault = new Schema({ toys: { type: [String], default: undefined } }); const ElementDefault = new Schema({ // 这里的 `default: undefined` 作用于每个字符串元素,而不是 `toys` toys: [{ type: String, default: undefined }] }); mongoose.model('ArrayDefault', ArrayDefault); mongoose.model('ElementDefault', ElementDefault); new (mongoose.model('ArrayDefault'))().toys; // undefined new (mongoose.model('ElementDefault'))().toys; // []

注意:指定空数组等价于Mixed。下面几种写法都创建Mixed数组:

const Empty1 = new Schema({ any: [] }); const Empty2 = new Schema({ any: Array }); const Empty3 = new Schema({ any: [Schema.Types.Mixed] }); const Empty4 = new Schema({ any: [{}] });

Maps

MongooseMap是 JavaScriptMap类的子类。在 Mongoose 中,map 是创建带任意键的嵌套文档的方式。

注意:在 Mongoose Map 中,键必须是字符串,这样才能在 MongoDB 中存储文档。

const userSchema = new Schema({ // `socialMediaHandles` 是一个值类型为字符串的 map。 // map 的键始终是字符串,用 `of` 指定值的类型。 socialMediaHandles: { type: Map, of: String } }); const User = mongoose.model('User', userSchema); // Map { 'github' => 'vkarpov15', 'twitter' => '@code_barbarian' } console.log(new User({ socialMediaHandles: { github: 'vkarpov15', twitter: '@code_barbarian' } }).socialMediaHandles);

上面的例子没有显式声明githubtwitter为 path,但因为socialMediaHandles是 map,可以存储任意键值对。不过由于它是 map,你必须.get()获取键值、用.set()设置键值:

const user = new User({ socialMediaHandles: {} }); // 正确 user.socialMediaHandles.set('github', 'vkarpov15'); // 也可以 user.set('socialMediaHandles.twitter', '@code_barbarian'); // 错误,`myspace` 属性不会被保存 user.socialMediaHandles.myspace = 'fail'; // 'vkarpov15' console.log(user.socialMediaHandles.get('github')); // '@code_barbarian' console.log(user.get('socialMediaHandles.twitter')); // undefined user.socialMediaHandles.github; // 只会保存 'github' 和 'twitter' 属性 user.save();

Map 类型在 MongoDB 中以 BSON 对象存储。BSON 对象的键是有序的,因此 map 的插入顺序特性得以保留。

Mongoose 支持特殊的$*语法来 populate map 中的所有元素。例如假设socialMediaHandlesmap 中包含一个ref

const userSchema = new Schema({ socialMediaHandles: { type: Map, of: new Schema({ handle: String, oauth: { type: ObjectId, ref: 'OAuth' } }) } }); const User = mongoose.model('User', userSchema);

要 populate 每个socialMediaHandles条目的oauth属性,应该 populatesocialMediaHandles.$*.oauth

const user = await User.findOne().populate('socialMediaHandles.$*.oauth');

Map 的底层实现位于 lib/schema/map.js,它继承自 JavaScript 原生Map并挂接到 Mongoose 的变更追踪机制;仓库 test/types.map.test.js 覆盖了 map 的读写、cast 与$*populate 场景。

UUID

Mongoose 还支持 UUID 类型,它把 UUID 实例以 Node.js buffer 形式存储。在 Node.js 中,UUID 表示为bson.Binary类型的实例,并带有一个 getter,在访问时把二进制转成字符串。Mongoose 在 MongoDB 中以 subtype 4 的二进制数据存储 UUID。

建议:在 Mongoose 中做唯一文档 id 时优先使用 ObjectId,只有在确有需要时才使用 UUID。

const authorSchema = new Schema({ _id: Schema.Types.UUID, // 也可以写 `_id: 'UUID'` name: String }); const Author = mongoose.model('Author', authorSchema); const bookSchema = new Schema({ authorId: { type: Schema.Types.UUID, ref: 'Author' } }); const Book = mongoose.model('Book', bookSchema); const author = new Author({ name: 'Martin Fowler' }); console.log(typeof author._id); // 'string' console.log(author.toObject()._id instanceof mongoose.mongo.BSON.Binary); // true const book = new Book({ authorId: '09190f70-3d30-11e5-8814-0f4df9a59c41' });

创建 UUID 时,推荐使用 Node 内置的 UUIDv4 生成器:

const { randomUUID } = require('crypto'); const schema = new mongoose.Schema({ docId: { type: 'UUID', default: () => randomUUID() } });

BigInt

Mongoose 支持把 JavaScript BigInt 作为 SchemaType。BigInt 在 MongoDB 中以 64 位整数(BSON 类型 "long")存储:

const questionSchema = new Schema({ answer: BigInt }); const Question = mongoose.model('Question', questionSchema); const question = new Question({ answer: 42n }); typeof question.answer; // 'bigint'

Double

Mongoose 支持把 64 位 IEEE 754-2008 浮点数作为 SchemaType。Double 在 MongoDB 中以 BSON 类型 "double" 存储:

const temperatureSchema = new Schema({ celsius: Double }); const Temperature = mongoose.model('Temperature', temperatureSchema); const temperature = new Temperature({ celsius: 1339 }); temperature.celsius instanceof bson.Double; // true

下面这些值都能成功 cast 为 Double:

new Temperature({ celsius: '1.2e12' }).celsius; // 1200000000000 作为 Double new Temperature({ celsius: true }).celsius; // 1 作为 Double new Temperature({ celsius: false }).celsius; // 0 作为 Double new Temperature({ celsius: { valueOf: () => 83.0033 } }).celsius; // 83 作为 Double new Temperature({ celsius: '' }).celsius; // null

以下输入会在验证阶段导致 CastError(初始化时不抛错,验证时才抛错):

  • 不代表数字字符串、NaN 或 null-ish 值的字符串
  • 没有valueOf()函数的对象
  • 超出 IEEE 754-2008 浮点数表示范围的值

从 lib/schema/double.js 源码可见,SchemaDouble的查询条件处理器($conditionalHandlers)为$gt/$gte/$lt/$lte等比较操作符单独注册了 cast 逻辑,并支持SchemaDouble.cast(caster)自定义 cast 函数(如把 NaN cast 成 0)。

Int32

Mongoose 支持把 32 位整数作为 SchemaType。Int32 在 MongoDB 中以 32 位整数(BSON 类型 "int")存储:

const studentSchema = new Schema({ id: Int32 }); const Student = mongoose.model('Student', studentSchema); const student = new Student({ id: 1339 }); typeof student.id; // 'number'

下面这些值都能成功 cast 为 Int32:

new Student({ id: '15' }).id; // 15 作为 Int32 new Student({ id: true }).id; // 1 作为 Int32 new Student({ id: false }).id; // 0 作为 Int32 new Student({ id: { valueOf: () => 83 } }).id; // 83 作为 Int32 new Student({ id: '' }).id; // null 作为 Int32

如果传入的对象带有返回 Number 的valueOf()函数,Mongoose 会调用它并把返回值赋给该 path。nullundefined不会被 cast。

以下输入会在验证阶段导致 CastError(初始化时不抛错,验证时才抛错):

  • NaN
  • 能 cast 成 NaN 的字符串
  • 没有valueOf()函数的对象
  • 必须四舍五入才能成为整数的小数
  • 超出 32 位整数范围的值

从 lib/schema/int32.js 源码可以看到,SchemaInt32的默认严格 caster 用INT32_MAX = 0x7FFFFFFFINT32_MIN = -0x80000000做边界检查(v !== (v | 0)还会校验是否为整数),并且它的$conditionalHandlers额外注册了$bitsAllClear$bitsAnyClear$bitsAllSet$bitsAnySet四个位运算操作符(见 lib/schema/operators/bitwise.js)。

Union

UnionSchemaType 允许一个 path 接受多种类型。Mongoose 会尝试把值 cast 为其中一种指定类型:

const schema = new Schema({ value: { type: Schema.Types.Union, of: [String, Number] } }); const Model = mongoose.model('Model', schema); // 两种都有效 —— Mongoose 接受任意一种类型 const doc1 = new Model({ value: 'hello' }); const doc2 = new Model({ value: 42 });
Casting 行为

当你给 Union path 赋值时,Mongoose 按顺序尝试把它 cast 为of数组中的每种类型。如果值与其中某个类型精确匹配(使用===),Mongoose 直接使用该值;否则使用第一个能成功 cast 该值的类型:

const schema = new Schema({ flexibleField: { type: Schema.Types.Union, of: [Number, Date] } }); const Model = mongoose.model('Model', schema); // Number 类型 const doc1 = new Model({ flexibleField: 42 }); doc1.flexibleField; // 42 (number) // 字符串 '42' 被 cast 为 Number(第一个成功的类型) const doc2 = new Model({ flexibleField: '42' }); doc2.flexibleField; // 42 (number) // Date 类型 const doc3 = new Model({ flexibleField: new Date('2025-06-01') }); doc3.flexibleField; // Date 对象 // 字符串日期被 cast 为 Date const doc4 = new Model({ flexibleField: '2025-06-01' }); doc4.flexibleField; // Date 对象

Union 的 cast 逻辑实现于 lib/schema/union.js:构造函数会校验options.of必须是非空数组(否则直接抛出'Union schema type requires an array of types'),并把每个成员通过parentSchema.interpretAsType()解释成真实的 SchemaType 实例存进this.schemaTypescast()方法依次调用每个子类型的 cast,命中=== val立即返回原值(避免 Number 被错误 cast 成 String/Date 等),否则取第一个成功 cast 的结果,全部失败则抛出最后一个类型的 cast 错误。

错误处理

如果 Mongoose 无法把值 cast 为任何指定类型,它会抛出 union 中最后一个类型产生的错误:

const schema = new Schema({ value: { type: Schema.Types.Union, of: [Number, Boolean] } }); const Model = mongoose.model('Model', schema); const doc = new Model({ value: 'not a number or boolean' }); // 抛出: Cast to Boolean failed for value "not a number or boolean"
Union 带选项

你可以为 union 中的单个类型指定选项,例如为字符串指定trim

const schema = new Schema({ value: { type: Schema.Types.Union, of: [ Number, { type: String, trim: true } ] } }); const Model = mongoose.model('Model', schema); const doc = new Model({ value: ' hello ' }); doc.value; // 'hello' (已 trim)
查询与更新

Union 类型同样适用于查询和更新。Mongoose 会根据 union 类型来 cast 查询过滤条件和更新操作:

const schema = new Schema({ value: { type: Schema.Types.Union, of: [Number, Date] } }); const Model = mongoose.model('Model', schema); await Model.create({ value: 42 }); // 用字符串查询 —— 会被 cast 为数字 const doc = await Model.findOne({ value: '42' }); doc.value; // 42 // 更新 await Model.findOneAndUpdate( { value: 42 }, { value: new Date('2025-06-01') } );

此外,Union.prototype.toJSONSchema()(见 lib/schema/union.js)会把 union 输出为 JSON Schema 的anyOf结构,可用于 MongoDB 的$jsonSchema校验或加密 schema 配置;applySetters()也会为 union 中的每个成员依次应用其 setter 后再 cast。仓库 test/schema.union.test.js 提供了覆盖上述 cast、错误处理、选项与查询更新场景的测试。

Getters

Getter 之于 path 就像 virtuals 之于整个文档。例如你想把用户头像存为相对路径,再在应用层拼接主机名,可以这样组织userSchema

const root = 'https://s3.amazonaws.com/mybucket'; const userSchema = new Schema({ name: String, picture: { type: String, get: v => `${root}${v}` } }); const User = mongoose.model('User', userSchema); const doc = new User({ name: 'Val', picture: '/123.png' }); doc.picture; // 'https://s3.amazonaws.com/mybucket/123.png' doc.toObject({ getters: false }).picture; // '/123.png'

通常只在基础类型 path上使用 getter,而不是数组或子文档。因为 getter 会覆盖访问 Mongoose path 时返回的内容,在对象上声明 getter 可能会移除 Mongoose 对该 path 的变更追踪:

const schema = new Schema({ arr: [{ url: String }] }); const root = 'https://s3.amazonaws.com/mybucket'; // 不好,不要这样做! schema.path('arr').get(v => { return v.map(el => Object.assign(el, { url: root + el.url })); }); // 之后 doc.arr.push({ key: String }); doc.arr[0]; // 'undefined',因为每次访问 `doc.arr` 都会创建新数组!

不要像上面那样在数组上声明 getter,而应该在url字符串上声明 getter。如果确实需要在嵌套文档或数组上声明 getter,请格外小心:

const schema = new Schema({ arr: [{ url: String }] }); const root = 'https://s3.amazonaws.com/mybucket'; // 正确:替代在 `arr` 上声明 getter 的做法 schema.path('arr.0.url').get(v => `${root}${v}`);

用 Schema 作为路径类型

要把某个 path 声明为另一个 schema,将type设置为子 schema 的实例即可。要基于子 schema 的形状设置默认值,直接设置一个默认值,文档创建期间该值会先按子 schema 定义被 cast 再设置:

const subSchema = new mongoose.Schema({ // 这里放一些 schema 定义 }); const schema = new mongoose.Schema({ data: { type: subSchema, default: {} } });

创建自定义 SchemaTypes

Mongoose 可以通过自定义 SchemaTypes 进行扩展,完整指南见 Custom SchemaTypes 文档。你也可以在插件站点搜索兼容的类型,例如 mongoose-long、mongoose-int32、mongoose-function 等。所有自定义类型最终都要像内置类型一样提供schemaName、cast 逻辑与OptionsConstructor(可参考 lib/schema/index.js 中内置类型的注册方式)。

schema.path()函数

schema.path()返回指定 path 实例化后的 schema type:

const sampleSchema = new Schema({ name: { type: String, required: true } }); console.log(sampleSchema.path('name')); // 输出类似: /** * SchemaString { * enumValues: [], * regExp: null, * path: 'name', * instance: 'String', * validators: ... * } */

可以用这个函数检查某个 path 的 schema type,包括它有哪些校验器以及类型是什么。例如前文验证schema.path('name') instanceof mongoose.Schema.Types.String就是通过它实现的。

进一步阅读与下一步

围绕 SchemaTypes 体系,建议按以下顺序继续深入本仓库:

  • 校验(Validation):requiredenummin/maxmatch等内置校验器的完整说明与 CastError 行为
  • Schema 指南:schema 定义、type关键字与 virtuals 的更多细节
  • Populate:populate选项与 Map 的$*populate 语法
  • Subdocs(子文档):document array 与子文档变更追踪
  • Dates 教程:Date 类型的更多使用细节
  • 自定义 SchemaTypes 指南:编写插件级自定义类型
  • Connections:在掌握 SchemaTypes 后学习连接管理,也是官方文档的"下一步"章节

掌握 SchemaTypes 之后,就可以继续学习 Mongoose 的 Connections,搭建完整的异步数据访问链路了。

【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongoose

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 13:39:58

2025五一杯C题实战:Python可复现预测流程与资源整合

简介:2025年五一杯C题全套参赛资源,面向备战数学建模竞赛的参赛团队、希望快速掌握完整解题流程的学习者,以及需要高质量参考材料的科研爱好者。围绕C题提供从赛题浅析、模型构建、代码实跑到结果输出的闭环方案,包含成品论文、可…

作者头像 李华
网站建设 2026/9/10 13:37:11

开源AI视觉分析工具PixelMentor:让影视后期修图更智能

我做了近十年的影视后期,最让我头疼的往往不是调色或剪辑本身,而是反反复复“看图”这个过程。拿到一组原始素材,先在软件里拖进拖出,盯着高光阴影看半天,再对照参考片一帧一帧比对,才能确定某个镜头到底该…

作者头像 李华
网站建设 2026/9/10 13:36:42

Docker容器数据持久化:核心挑战与三大实现方案

1. Docker容器数据持久化管理的核心挑战 容器技术的革命性在于其轻量化和瞬时性,但这也带来了数据管理的根本矛盾。当我们在开发环境中运行一个MySQL容器,所有数据默认存储在容器内部的可写层(writable layer)中。这个设计带来的直…

作者头像 李华
网站建设 2026/9/10 13:36:39

OpenCore Legacy Patcher 完整指南:让老 Mac 升级最新 macOS

OpenCore Legacy Patcher 完整指南:让老 Mac 升级最新 macOS 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher&#xff08…

作者头像 李华
网站建设 2026/9/10 13:35:53

Sora2小程序源码拆解:前后端分离实现图片生成视频

简介:Sora2小程序源码是一套覆盖前端与后端完整链路的小程序项目,核心功能是通过一张图片生成视频,适合对AI视频应用、微信小程序全栈开发感兴趣的初中级开发者学习与复用。压缩包共收录205个文件,其中101个PHP文件承担后端业务逻…

作者头像 李华
网站建设 2026/9/10 13:35:46

Python实现LDA主题模型:原理、代码与调参

简介:面向自然语言处理入门者与主题建模实践者,这份资源提供基于Python的LDA主题模型实现代码,可帮助理解从文本预处理、语料构建到模型训练与主题输出的完整流程。压缩包共12个文件,以Python脚本、conf配置、dat数据及log日志为主…

作者头像 李华