mongoose .find() method returns object with unwanted properties
Asked Answered
A

6

21

so, I've been working with mongoose for some time and I found some really weird stuff going on. It would be great if someone could enlighten me.

The thing is, when using the .find() method of mongoose, the object I get as response is full of properties I don't know where It came from (I'm guessing they are built-in properties, but whatever) and I want to iterate only through the properties I .select(). Got it? No? ok... explaining better:

I have my schema and model declared:

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

Then I want to find a document with the name, let's say, John and retrieve all but the 'name' field, so I go:

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

and log(results[0]) logs

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

So far, so good. But the problems is, now I want to iterate through these properties and check one by one, and I don't know for sure how many 'props' each result will have, so I wanted to do something like:

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

So, I'm hoping it will log 'prop1', 'prop2' and 'prop3', but no! Ok, I get props 1, 2 and 3, but also I get a lots of other properties and functions like: isNew, error, _maxListeners, _doc, etc. Not only these extras properties, I also get the 'name' property, the one I excluded from the selection (and it was excluded, like shown in the first log). Weird huh?

But wait! There's more! I've searched online and found some people saying "Dude, when iterating through object properties use the hasOwnProperty method!". So there I went:

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

the log result is a few properties (to be specific: $__, isNew, error, _maxListeners, _doc, _pres, _posts, save, _events) and doesnt include any of the props I wanted in the first place.

My question is, how can I iterate through only prop 1, 2 and 3, excluding these, I don't know, built-in properties and the one I explicitly excluded in the parameters? (ps: I was thinking of a solution that doesnt involve having to convert my object into an array, if thats possible)

Also, not a question per se, but for curiosity, where does these properties come from? Why do they appear in the for loop and not when I log the object? Why the property I excluded ('-name') also appears in the for loop? What the hell is hasOwnProperty for if it doesnt recognize the properties that were just logged?

Thanks for your time and help! Bye!

Anthem answered 10/2, 2015 at 22:14 Comment(2)
.toObject() would give you a plain object, maybe that would help? The extra properties are because what you are dealing with is an instance of a model, which is not a plain object.Vineland
thanks @Kevin B ! Still have a lot of questions but that indeed solved my problem! Post your comment as an answer so I can mark it right? BR!Anthem
J
54

Alternatively to Kevin B's answer, you can pass {lean: true} as an option:

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

In MongoDB, the documents are saved simply as objects. When Mongoose retrieves them, it casts them into Mongoose documents. In doing so it adds all those keys that are being included in your for loop. This is what allows you to use all the document methods. If you won't be using any of these, lean is a great option as it skips that entire process, increasing query speed. Potentially 3x as fast.

Jemimah answered 11/2, 2015 at 16:28 Comment(3)
I tried to run await myModel.find({status: 'requested'}, {lean: true}); but it did not work. I had to run it as await myModel.find({status: 'requested'}).lean().exec(); in order for it to work. Any idea why? Is it because {lean: true} can only come as a 3rd argument to the find() call?Biscay
Yes I ran into this also. await Model.find({status:'requested'}, {},{lean:true})Liquidator
passsing the {lean:true} solved a similar problem for me.Alcaraz
V
20

In this case .toObject would be enough to let your loop work the way you expect.

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

The extra properties you were getting originally are due to the fact that results is a collection of model instances that come with additional properties and methods that aren't available on normal objects. These properties and methods are what are coming up in your loop. By using toObject, you get a plain object without all of those additional properties and methods.

Vineland answered 10/2, 2015 at 22:31 Comment(3)
My next question, then - how can I spoof these 'hidden' properties, for testing?Jecoa
Thank you so much. I was having to JSON.parse(JSON.stringify(results)) on every result. Using .toObject() is so much cleaner and better in every way.Korwun
The reason that converting to JSON and back worked as well is because the document object’s .toJSON() method is just an alias of this .toObject() method, so the serialized format only contained the lean object.Sofar
H
1

The answers are great and I would like to add a little typescript utility that I added to my dbUtils class.

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

You can pass the mongoose document/sub-document or any plain JS object here and it'll return a corresponding JS object.

Hankins answered 17/1, 2021 at 14:46 Comment(0)
C
1

use lean() on the mongo query or pass {lean:true} argument eg myModel.find().lean()

Cockcrow answered 24/2, 2021 at 19:13 Comment(0)
W
1

TLDR: toObject() and lean() are 2 methods you need to get the JavaScript object, which is pointed out by the previous answers. My answer has a full example that illustrates the concept and how to use them.


When you use Mongoose API to query data (find, findOne, findById ..), Mongoose will give you an instance of Mongoose Document class in the response, which is different from your Javascript object.

You have some options to get your Javascript object, as described in the document :

  • using lean() method : Check out the document here
  • using toObject() method : Check out the document here

I created a test project to demonstrate these methods, feel free to test this :

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
}

One side note, when you use lean() method, Mongoose skips the step to convert the JavaScript object to the Mongoose document, which results in a better performance for your queries.

Wareing answered 7/8, 2022 at 9:10 Comment(0)
K
0

Just use this :

Object.keys(e.toJSON())
Karaganda answered 12/3, 2024 at 13:44 Comment(1)
Please add your explanation of how this answer would work differently over the accepted answer and adding any value.Hispanic

© 2022 - 2025 — McMap. All rights reserved.