SchemaTypes

SchemaTypes 处理路径 defaultsvalidationgetterssetters字段选择默认值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: 布尔值,指定查询的默认 projections
  • validate: 函数,为此属性添加 验证器功能
  • 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 索引

  • index: 布尔值,是否在此属性上定义 index
  • unique: 布尔值,是否在此属性上定义 唯一索引
  • sparse: 布尔值,是否在此属性上定义 稀疏索引
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

使用说明

字符串

要将路径声明为字符串,你可以使用 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 将调用它并将返回值分配给路径。

nullundefined 不进行强制转换。

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。 你可以使用 convertToTrueconvertToFalse 属性(即 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: [{}] });

映射

MongooseMapJavaScript 的 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);

上面的示例没有显式地将 githubtwitter 声明为路径,但是,由于 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-longmongoose-int32mongoose-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,让我们来看看 连接