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 索引

  • 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:正则表达式,创建一个 验证器,该验证器检查值是否与给定的正则表达式匹配
  • enum:数组,创建一个 验证器,该验证器检查值是否在给定的数组中。
  • minLength:数字,创建一个 验证器,该验证器检查值的长度是否不小于给定的数字
  • maxLength:数字,创建一个 验证器,该验证器检查值的长度是否不大于给定的数字
  • populate:对象,设置默认 填充选项

数字

  • min:数字,创建一个 验证器,该验证器检查值是否大于或等于给定的最小值。
  • max:数字,创建一个 验证器,该验证器检查值是否小于或等于给定的最大值。
  • enum:数组,创建一个 验证器,该验证器检查值是否严格等于给定数组中的某个值。
  • populate:对象,设置默认 填充选项

日期

  • min:日期,创建一个 验证器,该验证器检查值是否大于或等于给定的最小值。
  • max:日期,创建一个 验证器,该验证器检查值是否小于或等于给定的最大值。
  • expires:数字或字符串,使用以秒为单位表示的值创建 TTL 索引。

ObjectId

使用说明

字符串

要将路径声明为字符串,您可以使用 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]}

混合

一个“无所不包”的 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。您可以使用 convertToTrueconvertToFalse 属性修改 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: [{}] });

映射

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

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