find()方法返回具有不需要的属性的对象

mongoose .find() method returns object with unwanted properties

本文关键字:不需要 属性 对象 方法 返回 find      更新时间:2023-09-26

所以,我和猫鼬合作了一段时间,我发现了一些非常奇怪的事情。如果有人能启发我,那就太好了。

问题是,当使用mongoose的.find()方法时,我作为响应得到的对象充满了我不知道它来自哪里的属性(我猜它们是内置属性,但不管怎样),我只想迭代我的.select()属性。明白了吗?不好的…解释得更好:

我已经声明了我的模式和模型:

var mySchema = mongoose.Schema({
  name: String,
  prop1: String,
  prop2: String,
  prop3: String
})
var myModel = DB.model('myDataBase', mySchema)

然后我想找到一个名为John的文档,并检索除"name"字段外的所有字段,所以我选择:

myModel.find({name: 'John'}, '-name', function(err, results){
  log(results[0])
}

和log(results[0])logs

{ prop1: 'one',
  prop2: 'two',
  prop3: 'three' }

到目前为止,一切都很好。但问题是,现在我想迭代这些属性并逐一检查,我不确定每个结果会有多少"道具",所以我想做一些类似的事情:

for(var key in results[0]){
  log(key)
}

所以,我希望它会记录"prop1"、"prop2"answers"prop3",但不会!好吧,我得到了道具1、2和3,但我也得到了很多其他属性和函数,如:isNew、error、_maxListeners、_doc等。不仅这些额外的属性,我还得到了"name"属性,即我从选择中排除的属性(它被排除了,如第一个日志中所示)。奇怪吧?

但是等一下!还有更多!我在网上搜索过,发现有人说"老兄,当迭代对象属性时,使用hasOwnProperty方法!"。所以我去了:

for (var key in results[0]){
  if (results[0].hasOwnProperty(key)) log(key)
}

日志结果是一些属性(具体来说:$__、isNew、error、_maxListeners、_doc、_pres、_posts、save、_events),并且不包括我最初想要的任何道具。

我的问题是,除了这些我不知道的内置属性和我在参数中明确排除的属性,我如何只遍历道具1、2和3?(ps:如果可能的话,我正在考虑一个不需要将我的对象转换为数组的解决方案)

此外,这本身不是一个问题,但出于好奇,这些特性来自哪里?为什么它们出现在for循环中,而不是在我记录对象时?为什么我排除的属性("-name")也出现在for循环中?如果hasOwnProperty不识别刚刚记录的属性,它到底是干什么的?

感谢您的时间和帮助!再见

除了Kevin B的答案之外,您还可以将{lean: true}作为选项传递:

myModel.find({name: 'John'}, '-name', {lean: true}, function(err, results){
  log(results[0])
}

在MongoDB中,文档被简单地保存为对象。当Mongoose检索它们时,它将它们转换为Mongoose文档。在这样做的过程中,它会添加for循环中包含的所有密钥。这就是允许您使用所有文档方法的原因。如果您不打算使用其中任何一个,lean是一个很好的选择,因为它跳过了整个过程,提高了查询速度。可能快3倍。

在这种情况下,.toObject足以让您的循环按预期方式工作。

myModel.find({name: 'John'}, '-name', function(err, results){
  log(results[0].toObject())
}

您最初获得的额外属性是因为results是一个模型实例的集合,它附带了普通对象上不可用的额外属性和方法。这些属性和方法将出现在您的循环中。通过使用toObject,您可以获得一个没有所有这些附加属性和方法的普通对象。

对mongo查询使用lean()或传递{lean:true}参数例如myModel.find().lean()

TLDR:toObject()lean()获取JavaScript对象所需的两种方法,前面的答案已经指出了这一点。我的答案有一个完整的例子,说明了这个概念以及如何使用它们


当您使用Mongoose API查询数据(find,findOne,findById..)时,Mongoose会在响应中为您提供Mongoose Document类的实例,这与您的Javascript对象不同。

您可以选择一些选项来获取Javascript对象,如文档中所述:

  • 使用lean()方法:在此处查看文档
  • 使用toObject()方法:在此处查看文档

我创建了一个测试项目来演示这些方法,可以自由测试:

const mongoose = require('mongoose');
// connect to database
mongoose.connect('mongodb://localhost/test', { useNewUrlParser: true, useUnifiedTopology: true });
// define the schema 
const kittySchema = new mongoose.Schema({
    name: String
    // this flag indicate that the shema we defined is not fixed, 
    // document in database can have some fields that are not defined in the schema
    // which is very likely
}, { strict: false });
// compile schema to model
const Kitten = mongoose.model('Kitten', kittySchema);
test();
async function test() {

    // test data
    const dataObject = { name: "Kitty 1", color: "red" };
    const firstKitty = new Kitten(dataObject); // attribute color is not defined in the schema
    // save in database
    firstKitty.save();
    // find the kitty from database
    // mongoose return a document object, which is different from our data object
    const firstKittyDocument = await Kitten.findOne({ name: "Kitty 1" });
    console.log("Mongoose document. _id :", firstKittyDocument._id); // _id of document
    console.log("Mongoose document. name :", firstKittyDocument.name); // "Kitty 1"
    console.log("Mongoose document. color :", firstKittyDocument.color); // undefined
    // --> the document contains _id and other fields that we defined in the schema
    // we can call the method .toObject to get the plain object
    console.log("Using .toObject() method. _id :", firstKittyDocument.toObject()._id); // _id of document
    console.log("Using .toObject() method. name :", firstKittyDocument.toObject().name); // "Kitty 1"
    console.log("Using .toObject() method. color :", firstKittyDocument.toObject().color); // "red"
    // --> Using .toObject() method, we get all the fields we have in the dataObject
    // or we can use lean method to get the plain old javascript object
    const firstKittyPOJO = await Kitten.findOne({ name: "Kitty 1" }).lean();
    console.log("Using .lean() method. _id :", firstKittyPOJO._id);  // _id of document
    console.log("Using .lean() method. name :", firstKittyPOJO.name); // "Kitty 1"
    console.log("Using .lean() method. color :", firstKittyPOJO.color); //"red"
    // --> Using .lean() method, we get all the fields we have in the dataObject
}

需要注意的是,当您使用lean()方法时,Mongoose会跳过将JavaScript对象转换为Mongoose文档的步骤,这将为您的查询带来更好的性能。

答案很好,我想添加一个我添加到dbUtils类中的小typescript实用程序。

getCleanObjectFromObjectOrDocument<T>(obj: T): T {
        return ((obj as unknown) as Document)?.toObject?.() ?? obj;
}

你可以在这里传递mongoose文档/子文档或任何普通的JS对象,它会返回相应的JS对象。