SchemaTypes (綱要類型)
SchemaTypes 處理路徑的 預設值、驗證、getter、setter、欄位選擇預設值,用於 查詢,以及 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 ObjectIds,它只是綱要中路徑的設定。
以下是 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
});
除了 type 屬性之外,您還可以為路徑指定其他屬性。例如,如果您想在儲存之前將字串轉換為小寫
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
:任何或函數,設定路徑的預設值。如果值為函數,則函數的傳回值將用作預設值。select
:布林值,指定查詢的預設投影validate
:函數,為此屬性新增驗證器函數get
:函數,使用Object.defineProperty()
為此屬性定義自訂 getter。set
:函數,使用Object.defineProperty()
為此屬性定義自訂 setter。alias
:字串,僅限 mongoose >= 4.10.0。定義具有指定名稱的虛擬,該虛擬會取得/設定此路徑。immutable
:布林值,將路徑定義為不可變。除非父文件具有isNew: true
,否則 Mongoose 會阻止您變更不可變的路徑。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
:RegExp,建立一個驗證器,檢查值是否符合給定的正規表示式enum
:陣列,建立一個驗證器,檢查值是否在給定的陣列中。minLength
:數字,建立一個驗證器,檢查值的長度是否不小於給定的數字maxLength
:數字,建立一個驗證器,檢查值的長度是否不大於給定的數字populate
:物件,設定預設填充選項
數字
min
:數字,建立一個驗證器,檢查值是否大於或等於給定的最小值。max
:數字,建立一個驗證器,檢查值是否小於或等於給定的最大值。enum
:陣列,建立一個驗證器,檢查值是否嚴格等於給定陣列中的其中一個值。populate
:物件,設定預設填充選項
日期
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'
。
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 Mixed 類型的值已變更,您需要呼叫 doc.markModified(path)
,傳遞您剛變更之 Mixed 類型的路徑。
為了避免這些副作用,可以使用子文件路徑來代替。
person.anything = { x: [3, 4, { y: 'changed' }] };
person.markModified('anything');
person.save(); // Mongoose will save changes to `anything`.
ObjectIds
ObjectId 是一種特殊類型,通常用於唯一識別碼。以下是如何使用路徑 driver
(屬於 ObjectId) 宣告綱要的方法
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
屬性來修改 Mongoose 轉換為 true 或 false 的值,這些屬性是 JavaScript sets。
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
類別的子類別。在這些文件中,我們將交替使用「映射」和 MongooseMap
這兩個詞。在 Mongoose 中,映射是您如何建立具有任意鍵的巢狀文件。
注意:在 Mongoose 映射中,為了將文件儲存在 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
是一個映射,您必須使用 .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 支援特殊的 $*
語法來 擴展映射中的所有元素。例如,假設您的 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');
UUID
Mongoose 也支援 UUID 類型,該類型將 UUID 實例儲存為 Node.js 緩衝區。我們建議在 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()
}
});
BigInt
Mongoose 支援 JavaScript BigInts 作為 SchemaType。BigInts 在 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'
Getters (getter)
Getters 類似於您架構中定義的路徑的虛擬屬性。例如,假設您想要將使用者個人資料圖片儲存為相對路徑,然後在您的應用程式中新增主機名稱。以下是您如何建構 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'
一般來說,您只會將 getters 用於原始路徑,而不是陣列或子文件。由於 getters 會覆寫存取 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}`);
Schemas (綱要)
若要將路徑宣告為另一個架構,請將 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
,讓我們來看看連線。