SchemaTypes
SchemaTypes 处理路径的定义 默认值、验证、获取器、设置器、字段选择默认值 用于 查询 以及 Mongoose 文档属性的其他一般特性。
什么是 SchemaType?
您可以将 Mongoose 模式视为 Mongoose 模型的配置对象。然后,SchemaType 是单个属性的配置对象。SchemaType 说明给定路径应该是什么类型,它是否具有任何获取器/设置器,以及哪些值为该路径有效。
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,它只是一个模式中路径的配置。
以下是 Mongoose 中所有有效的 SchemaTypes。Mongoose 插件还可以添加自定义 SchemaTypes,例如 int32。查看 Mongoose 的插件搜索 以查找插件。
示例
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,
_someId: Schema.Types.ObjectId,
decimal: Schema.Types.Decimal128,
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 模式中的一个特殊属性。当 Mongoose 在您的模式中找到名为 type
的嵌套属性时,Mongoose 假设它需要使用给定类型定义 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
(股票、债券、ETF 等)。天真地,您可能会像下面这样定义您的模式
const holdingSchema = new Schema({
// You might expect `asset` to be an object that has 2 properties,
// but unfortunately `type` is special in Mongoose so mongoose
// interprets this schema to mean that `asset` is a string
asset: {
type: String,
ticker: String
}
});
但是,当 Mongoose 看到 type: String
时,它会假设您的意思是 asset
应该是一个字符串,而不是一个具有 type
属性的对象。在下面显示了定义具有 type
属性的对象的正确方法。
const holdingSchema = new Schema({
asset: {
// Workaround to make sure Mongoose knows `asset` is an object
// and `asset.type` is a string, rather than thinking `asset`
// is a string.
type: { type: String },
ticker: String
}
});
SchemaType 选项
您可以使用类型本身或具有 type
属性的对象来声明模式类型。
const schema1 = new Schema({
test: String // `test` is a path of type String
});
const schema2 = new Schema({
// The `test` object contains the "SchemaType options"
test: { type: String } // `test` is a path of type string
});
除了类型属性之外,您还可以为路径指定其他属性。例如,如果您想在保存之前将字符串转换为小写
const schema2 = new Schema({
test: {
type: String,
lowercase: true // Always convert `test` to lowercase
}
});
您可以向 SchemaType 选项添加任何您想要的属性。许多插件依赖于自定义 SchemaType 选项。例如,mongoose-autopopulate 插件会在您在 SchemaType 选项中设置 autopopulate: true
时自动填充路径。Mongoose 附带对几个内置 SchemaType 选项的支持,例如上面示例中的 lowercase
。
lowercase
选项仅适用于字符串。某些选项适用于所有模式类型,而某些选项适用于特定模式类型。
所有 Schema 类型
required
:布尔值或函数,如果为 true,则为该属性添加一个 必需验证器default
:任何值或函数,为路径设置默认值。如果该值为函数,则该函数的返回值将用作默认值。select
:布尔值,指定查询的默认 投影validate
:函数,为该属性添加一个 验证器函数get
:函数,使用Object.defineProperty()
为该属性定义自定义获取器。set
:函数,使用Object.defineProperty()
为该属性定义自定义设置器。alias
:字符串,仅限 mongoose >= 4.10.0。使用给定名称定义一个 虚拟字段,该字段获取/设置此路径。immutable
:布尔值,将路径定义为不可变的。Mongoose 阻止您更改不可变路径,除非父文档的isNew: true
。transform
:函数,当您调用Document#toJSON()
函数时,Mongoose 会调用此函数,包括当您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
索引
您还可以使用模式类型选项定义 MongoDB 索引。
const schema2 = new Schema({
test: {
type: String,
index: true,
unique: true // Unique index. If you specify `unique: true`
// specifying `index: true` is optional if you do `unique: true`
}
});
字符串
lowercase
:布尔值,是否始终对值调用.toLowerCase()
uppercase
:布尔值,是否始终对值调用.toUpperCase()
trim
:布尔值,是否始终对值调用.trim()
match
:正则表达式,创建一个 验证器,该验证器检查值是否与给定的正则表达式匹配enum
:数组,创建一个 验证器,该验证器检查值是否在给定的数组中。minLength
:数字,创建一个 验证器,该验证器检查值的长度是否不小于给定的数字maxLength
:数字,创建一个 验证器,该验证器检查值的长度是否不大于给定的数字populate
:对象,设置默认 填充选项
数字
min
:数字,创建一个 验证器,该验证器检查值是否大于或等于给定的最小值。max
:数字,创建一个 验证器,该验证器检查值是否小于或等于给定的最大值。enum
:数组,创建一个 验证器,该验证器检查值是否严格等于给定数组中的某个值。populate
:对象,设置默认 填充选项
日期
min
:日期,创建一个 验证器,该验证器检查值是否大于或等于给定的最小值。max
:日期,创建一个 验证器,该验证器检查值是否小于或等于给定的最大值。expires
:数字或字符串,使用以秒为单位表示的值创建 TTL 索引。
ObjectId
populate
:对象,设置默认 填充选项
使用说明
字符串
要将路径声明为字符串,您可以使用 String
全局构造函数或字符串 'String'
。
const schema1 = new Schema({ name: String }); // name will be cast to string
const schema2 = new Schema({ name: 'String' }); // Equivalent
const Person = mongoose.model('Person', schema2);
如果您传递一个具有 toString()
函数的元素,Mongoose 将调用它,除非该元素是一个数组或 toString()
函数严格等于 Object.prototype.toString()
。
new Person({ name: 42 }).name; // "42" as a string
new Person({ name: { toString: () => 42 } }).name; // "42" as a string
// "undefined", will get a cast error if you `save()` this document
new Person({ name: { foo: 42 } }).name;
数字
要将路径声明为数字,您可以使用 Number
全局构造函数或字符串 'Number'
。
const schema1 = new Schema({ age: Number }); // age will be cast to a Number
const schema2 = new Schema({ age: 'Number' }); // Equivalent
const Car = mongoose.model('Car', schema2);
有几种类型的将成功转换为数字的值。
new Car({ age: '15' }).age; // 15 as a Number
new Car({ age: true }).age; // 1 as a Number
new Car({ age: false }).age; // 0 as a Number
new Car({ age: { valueOf: () => 83 } }).age; // 83 as a Number
如果您传递一个具有 valueOf()
函数的对象,该函数返回一个数字,Mongoose 将调用它并将返回值分配给路径。
null
和 undefined
值不会被转换。
NaN、转换为 NaN 的字符串、数组以及没有 valueOf()
函数的对象,在验证后都将导致 CastError,这意味着它不会在初始化时抛出异常,而是在验证时抛出异常。
日期
内置 Date
方法 没有挂钩到 mongoose 更改跟踪逻辑,用英语来说,这意味着如果您在文档中使用 Date
并使用 setMonth()
等方法对其进行修改,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(); // THIS DOES NOT SAVE YOUR CHANGE
doc.markModified('dueDate');
await doc.save(); // works
缓冲区
要将路径声明为 Buffer,您可以使用 Buffer
全局构造函数或字符串 'Buffer'
。
const schema1 = new Schema({ binData: Buffer }); // binData will be cast to a Buffer
const schema2 = new Schema({ binData: 'Buffer' }); // Equivalent
const Data = mongoose.model('Data', schema2);
Mongoose 将成功将以下值转换为缓冲区。
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]}
混合
一个“无所不包”的 SchemaType。Mongoose 不会对混合路径进行任何转换。您可以使用 Schema.Types.Mixed
或传递一个空对象字面量来定义混合路径。以下等价。
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 是一个无模式类型,因此您可以将值更改为任何您喜欢的其他内容,但 Mongoose 会失去自动检测和保存这些更改的能力。要告诉 Mongoose 混合类型的的值已更改,您需要调用 doc.markModified(path)
,将您刚刚更改的混合类型的路径传递给它。
为了避免这些副作用,可以使用 子文档 路径代替。
person.anything = { x: [3, 4, { y: 'changed' }] };
person.markModified('anything');
person.save(); // Mongoose will save changes to `anything`.
ObjectId
ObjectId 是一种特殊类型,通常用于唯一标识符。以下是如何声明具有 driver
路径的模式,该路径是一个 ObjectId
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(); // Something like "5e1a0651741b255ddda996c4"
布尔值
Mongoose 中的布尔值是 纯 JavaScript 布尔值。默认情况下,Mongoose 将以下值转换为 true
true
'true'
1
'1'
'yes'
Mongoose 将以下值转换为 false
false
'false'
0
'0'
'no'
任何其他值都会导致 CastError。您可以使用 convertToTrue
和 convertToFalse
属性修改 Mongoose 转换为 true 或 false 的值,它们是 JavaScript 集合。
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
数组
Mongoose 支持 SchemaTypes 数组和 子文档 数组。SchemaTypes 数组也称为基本数组,子文档数组也称为文档数组。
const ToySchema = new Schema({ name: String });
const ToyBoxSchema = new Schema({
toys: [ToySchema],
buffers: [Buffer],
strings: [String],
numbers: [Number]
// ... etc
});
数组很特殊,因为它们隐式地具有 []
(空数组)的默认值。
const ToyBox = mongoose.model('ToyBox', ToyBoxSchema);
console.log((new ToyBox()).toys); // []
要覆盖此默认值,您需要将默认值设置为 undefined
const ToyBoxSchema = new Schema({
toys: {
type: [ToySchema],
default: undefined
}
});
注意:指定一个空数组等效于 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: [{}] });
映射
MongooseMap
是 JavaScript 的 Map
类 的子类。在这些文档中,我们将交替使用“map”和 MongooseMap
术语。在 Mongoose 中,map 是创建具有任意键的嵌套文档的方式。
注意:在 Mongoose Maps 中,键必须是字符串,以便将文档存储在 MongoDB 中。
const userSchema = new Schema({
// `socialMediaHandles` is a map whose values are strings. A map's
// keys are always strings. You specify the type of values using `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);
上面的示例没有明确声明 github
或 twitter
作为路径,但是由于 socialMediaHandles
是一个 map,您可以存储任意键值对。但是,由于 socialMediaHandles
是一个 map,您必须使用 .get()
获取键的值,并使用 .set()
设置键的值。
const user = new User({
socialMediaHandles: {}
});
// Good
user.socialMediaHandles.set('github', 'vkarpov15');
// Works too
user.set('socialMediaHandles.twitter', '@code_barbarian');
// Bad, the `myspace` property will **not** get saved
user.socialMediaHandles.myspace = 'fail';
// 'vkarpov15'
console.log(user.socialMediaHandles.get('github'));
// '@code_barbarian'
console.log(user.get('socialMediaHandles.twitter'));
// undefined
user.socialMediaHandles.github;
// Will only save the 'github' and 'twitter' properties
user.save();
Map 类型存储为 MongoDB 中的 BSON 对象。BSON 对象中的键是有序的,这意味着 map 的 插入顺序 属性将被维护。
Mongoose 支持特殊的 $*
语法来 填充 map 中的所有元素。例如,假设您的 socialMediaHandles
map 包含一个 ref
const userSchema = new Schema({
socialMediaHandles: {
type: Map,
of: new Schema({
handle: String,
oauth: {
type: ObjectId,
ref: 'OAuth'
}
})
}
});
const User = mongoose.model('User', userSchema);
要填充每个 socialMediaHandles
条目的 oauth
属性,您应该在 socialMediaHandles.$*.oauth
上进行填充。
const user = await User.findOne().populate('socialMediaHandles.$*.oauth');
UUID
Mongoose 还支持 UUID 类型,该类型将 UUID 实例存储为 Node.js 缓冲区。我们建议使用 ObjectId 而不是 UUID 作为 Mongoose 中唯一文档 ID,但如果您需要,可以使用 UUID。
在 Node.js 中,UUID 表示为 bson.Binary
类型的实例,带有 getter,当您访问它时,它会将二进制转换为字符串。Mongoose 将 UUID 存储为 MongoDB 中的子类型为 4 的二进制数据。
const authorSchema = new Schema({
_id: Schema.Types.UUID, // Can also do `_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'
获取器
Getter 类似于为您的模式中定义的路径创建的虚拟属性。例如,假设您想将用户个人资料图片存储为相对路径,然后在您的应用程序中添加主机名。以下是您构建 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'
通常,您只对原始路径使用 getter,而不是数组或子文档。因为 getter 会覆盖访问 Mongoose 路径返回的结果,所以对对象声明 getter 可能会删除 Mongoose 对该路径的更改跟踪。
const schema = new Schema({
arr: [{ url: String }]
});
const root = 'https://s3.amazonaws.com/mybucket';
// Bad, don't do this!
schema.path('arr').get(v => {
return v.map(el => Object.assign(el, { url: root + el.url }));
});
// Later
doc.arr.push({ key: String });
doc.arr[0]; // 'undefined' because every `doc.arr` creates a new array!
与其像上面那样在数组上声明 getter,不如像下面那样在 url
字符串上声明 getter。如果您需要在嵌套文档或数组上声明 getter,请务必小心!
const schema = new Schema({
arr: [{ url: String }]
});
const root = 'https://s3.amazonaws.com/mybucket';
// Good, do this instead of declaring a getter on `arr`
schema.path('arr.0.url').get(v => `${root}${v}`);
模式
要将路径声明为另一个 模式,请将 type
设置为子模式的实例。
要根据子模式的形状设置默认值,只需设置默认值,该值将在文档创建期间设置之前根据子模式的定义进行转换。
const subSchema = new mongoose.Schema({
// some schema definition here
});
const schema = new mongoose.Schema({
data: {
type: subSchema,
default: {}
}
});
创建自定义类型
Mongoose 还可以使用 自定义 SchemaTypes 进行扩展。在 插件 网站上搜索兼容的类型,例如 mongoose-long、mongoose-int32 和 mongoose-function。
阅读有关创建自定义 SchemaTypes 的更多信息 此处。
schema.path()
函数
schema.path()
函数返回给定路径的实例化模式类型。
const sampleSchema = new Schema({ name: { type: String, required: true } });
console.log(sampleSchema.path('name'));
// Output looks like:
/**
* SchemaString {
* enumValues: [],
* regExp: null,
* path: 'name',
* instance: 'String',
* validators: ...
*/
您可以使用此函数检查给定路径的模式类型,包括它具有的验证器以及类型是什么。
进一步阅读
下一步
既然我们已经介绍了 SchemaTypes
,让我们看看 连接。