SchemaTypes
SchemaTypes 处理路径 defaults、validation、getters、setters、字段选择默认值 和 queries 的定义,以及 Mongoose 文档属性的其他一般特性。
什么是 SchemaType?
你可以将 Mongoose 结构视为 Mongoose 模型的配置对象。 SchemaType 是单个属性的配置对象。 SchemaType 说明给定路径应具有什么类型、是否有任何 getter/setter 以及哪些值对该路径有效。
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 中所有有效的 SchemaType。 Mongoose 插件还可以添加自定义 SchemaType,例如 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
}
});
结构类型选项
你可以直接使用类型来声明结构类型,也可以使用具有 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 选项。 例如,如果你在 SchemaType 选项中设置 autopopulate: true
,则 mongoose-autopopulate 插件会自动填充路径。 Mongoose 支持多个内置 SchemaType 选项,例如上面示例中的 lowercase
。
lowercase
选项仅适用于字符串。 有些选项适用于所有结构类型,有些选项适用于特定结构类型。
所有结构类型
required
: 布尔值或函数,如果为 true,则为此属性添加 所需的验证器default
: Any 或 函数,设置路径的默认值。 如果该值是一个函数,则该函数的返回值将用作默认值。select
: 布尔值,指定查询的默认 projectionsvalidate
: 函数,为此属性添加 验证器功能get
: 函数,使用Object.defineProperty()
. 为该属性定义一个自定义 getter。set
: 函数,使用Object.defineProperty()
.函数为此属性定义一个自定义设置器。alias
: 字符串,仅限 mongoose >= 4.10.0。 使用给定名称定义获取/设置此路径的 virtual。immutable
: 布尔值,将路径定义为不可变。 Mongoose 会阻止你更改不可变路径,除非父文档具有isNew: true
。transform
: 函数,Mongoose 在调用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
索引
你还可以使用结构类型选项定义 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
: RegExp,创建一个 validator,检查该值是否与给定的正则表达式匹配enum
: 数组,创建一个 validator 来检查该值是否在给定数组中。minLength
: Number,创建一个 validator,检查值长度是否不小于给定数字maxLength
: Number,创建一个 validator,检查值长度是否不大于给定数字populate
: 对象,设置默认 填充选项
数字
min
: Number,创建一个 validator,检查该值是否大于或等于给定的最小值。max
: Number,创建一个 validator,检查该值是否小于或等于给定的最大值。enum
: 数组,创建一个 validator,检查该值是否严格等于给定数组中的值之一。populate
: 对象,设置默认 填充选项
日期
min
: 日期,创建一个 validator,检查该值是否大于或等于给定的最小值。max
: 日期,创建一个 validator,检查该值是否小于或等于给定的最大值。expires
: 数字或字符串,创建一个 TTL 索引,其值以秒为单位表示。
对象 ID
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]}
混合
"什么都可以" 结构类型。 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 Mixed 类型的值已更改,你需要调用 doc.markModified(path)
,将路径传递给刚刚更改的 Mixed 类型。
为了避免这些副作用,可以改用 子文档 路径。
person.anything = { x: [3, 4, { y: 'changed' }] };
person.markModified('anything');
person.save(); // Mongoose will save changes to `anything`.
对象 ID
ObjectId 是一种特殊类型,通常用于唯一标识符。 以下是如何声明具有作为 ObjectId 的路径 driver
的结构:
const mongoose = require('mongoose');
const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId });
ObjectId
是一个类,ObjectIds 是对象。 但是,它们通常表示为字符串。 当你使用 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
属性(即 JavaScript 集)修改 Mongoose 将哪些值转换为 true 或 false。
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 数组和 subdocuments 数组。 SchemaType 数组也称为原始数组,子文档数组也称为 文档数组。
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 中,映射是你使用任意键创建嵌套文档的方式。
注意: 在 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
是一个映射,因此你可以存储任意键/值对。 但是,由于 socialMediaHandles
是一个映射,因此你 must 使用 .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();
映射类型存储为 MongoDB 中的 BSON 对象。 BSON 对象中的键是有序的,因此这意味着映射的 插入顺序 属性得到维护。
Mongoose 支持特殊的 $*
语法来 populate 映射中的所有元素。
例如,假设你的 socialMediaHandles
映射包含 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');
通用唯一标识符
Mongoose 还支持将 UUID 实例存储为 Node.js buffers 的 UUID 类型。 我们建议在 Mongoose 中使用 ObjectIds 而不是 UUID 作为唯一文档 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()
}
});
大整型
Mongoose 支持 JavaScript 大整数 作为 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!
你应该在 url
字符串上声明 getter,而不是如上所示在数组上声明 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}`);
结构
要将路径声明为另一个 schema,请将 type
设置为子结构的实例。
要根据子结构的形状设置默认值,只需设置一个默认值,该值将根据子结构的定义进行转换,然后在文档创建期间进行设置。
const subSchema = new mongoose.Schema({
// some schema definition here
});
const schema = new mongoose.Schema({
data: {
type: subSchema,
default: {}
}
});
创建自定义类型
Mongoose 也可以用 自定义结构类型 进行扩展。 在 plugins 网站上搜索兼容类型,例如 mongoose-long、mongoose-int32 和 mongoose-function。
阅读有关创建自定义 SchemaType here 的更多信息。
`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
,让我们来看看 连接。