280

I have a document from a mongoose find that I want to extend before JSON encoding and sending out as a response. If I try adding properties to the doc it is ignored. The properties don't appear in Object.getOwnPropertyNames(doc) making a normal extend not possible. The strange thing is that JSON.parse(JSON.encode(doc)) works and returns an object with all of the correct properties. Is there a better way to do this?

Lior Cohen
  • 8,985
  • 2
  • 29
  • 27
respectTheCode
  • 42,348
  • 18
  • 73
  • 86

9 Answers9

397

Mongoose Models inherit from Documents, which have a toObject() method. I believe what you're looking for should be the result of doc.toObject().

http://mongoosejs.com/docs/api.html#document_Document-toObject

Tamlyn
  • 22,122
  • 12
  • 111
  • 127
jmar777
  • 38,796
  • 11
  • 66
  • 64
  • 3
    Does this work if you return array on `Model.find({})` the docs return is an array. Can you docs.toObject? – jack blank Feb 15 '17 at 19:46
  • 7
    @jackblank if you have an array of Models, then you should be able to map over them: `var docArray = modelArray.map(function(model) { return model.toObject(); });` – jmar777 Feb 16 '17 at 14:58
  • @jmar777 i think your suggestion belong as an edit on this answer. – r3wt Nov 14 '17 at 20:44
  • 1
    This sadly doesn't work for nested items afaik - the objects in my array are still mongoose special ones – Dominic Mar 11 '18 at 15:34
  • 1
    Instead of doing toObject for every document is there is any global way to do this @jmar777 – devansvd May 27 '20 at 08:19
264

Another way to do this is to tell Mongoose that all you need is a plain JavaScript version of the returned doc by using lean() in the query chain. That way Mongoose skips the step of creating the full model instance and you directly get a doc you can modify:

MyModel.findOne().lean().exec(function(err, doc) {
    doc.addedProperty = 'foobar';
    res.json(doc);
});
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • Node says doc has no toObject() method but .lean() works like magic! – salihcenap May 30 '14 at 18:37
  • This seems like the better way to go, is one generally preferred or more performant? – Startec May 04 '15 at 04:20
  • 27
    @Startec Using `lean` is generally more performant because you skip the overhead of first creating the full Mongoose document. – JohnnyHK May 04 '15 at 04:34
  • 6
    you are a champion :) correct me if I am wrong, but you should always lean() your find()s if you do not intend to modify and save the document you just received (say, if you are just trying to access the doc and send it back to your client) – Amarsh Jun 24 '15 at 03:49
  • 1
    @Amarsh Yes, although that's also assuming you don't need any of the instance methods or virtuals defined in the schema. – JohnnyHK Jun 24 '15 at 04:23
  • 1
    bizzare though ... this could have been an option with find() ... its often too common to read a document from a mongodb and send it back to a web browser. i wonder if find().lean() actually means find() first and then apply a lean() of each element in the result, in which case, find().lean() combination would actually be slower than find() itself. – Amarsh Jun 24 '15 at 08:01
  • @Amarsh - did you come to any conclusions regarding comparative performance? – UpTheCreek Oct 09 '15 at 14:43
  • 6
    @Amarsh No, chaining `lean()` on the query sets the option before the query is actually executed (by `exec`) so that the results are directly in their "lean" form. See [this question](http://stackoverflow.com/questions/15097375/mongoose-node-js-module-causes-high-cpu-usage) for the positive performance impact side of calling `lean`. – JohnnyHK Oct 09 '15 at 14:48
  • I am getting great performance boots with .lean() also. something around 70% faster queries in some cases – Andy Macleod Sep 30 '16 at 09:26
  • Is there option to run `lean()` with `{getters: true}`? – Marecky Jul 16 '19 at 12:14
  • OR Is there an option to use `toObject()` or `lean()` and also get `id` field instead of `_id`? – Marecky Jul 16 '19 at 12:38
  • I spent hours figuring out why I wasn't able to add properties dynamically to existing data. In my context I didn't realize that I was working with mongodb document. adding lean() to mongoose.model().findById() worked perfectly for me – Akash Sarode Mar 29 '21 at 07:42
36

JohnnyHK suggestion:

In some cases as @JohnnyHK suggested, you would want to get the Object as a Plain Javascript. as described in this Mongoose Documentation there is another alternative to query the data directly as object:

const docs = await Model.find().lean();

Conditionally return Plain Object:

In addition if someone might want to conditionally turn to an object,it is also possible as an option argument, see find() docs at the third parameter:

const toObject = true;
const docs = await Model.find({},null,{lean:toObject});

its available on the functions: find(), findOne(), findById(), findOneAndUpdate(), and findByIdAndUpdate().

NOTE:

it is also worth mentioning that the _id attribute isn't a string object as if you would do JSON.parse(JSON.stringify(object)) but a ObjectId from mongoose types, so when comparing it to strings cast it to string before: String(object._id) === otherStringId

adir abargil
  • 5,495
  • 3
  • 19
  • 29
30

the fast way if the property is not in the model :

document.set( key,value, { strict: false });

alban maillere
  • 1,038
  • 10
  • 7
  • 7
    yes... and as suprising as it can look, I still think this could be usefull. For instance you keep all your object's primitives. – alban maillere Oct 21 '14 at 09:50
19

A better way of tackling an issue like this is using doc.toObject() like this

doc.toObject({ getters: true })

other options include:

  • getters: apply all getters (path and virtual getters)
  • virtuals: apply virtual getters (can override getters option)
  • minimize: remove empty objects (defaults to true)
  • transform: a transform function to apply to the resulting document before returning
  • depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
  • versionKey: whether to include the version key (defaults to true)

so for example you can say

Model.findOne().exec((err, doc) => {
   if (!err) {
      doc.toObject({ getters: true })
      console.log('doc _id:', doc._id)
   }
})

and now it will work.

For reference, see: http://mongoosejs.com/docs/api.html#document_Document-toObject

Jalasem
  • 27,261
  • 3
  • 21
  • 29
8

To get plain object from Mongoose document, I used _doc property as follows

mongooseDoc._doc  //returns plain json object

I tried with toObject but it gave me functions,arguments and all other things which i don't need.

dd619
  • 5,910
  • 8
  • 35
  • 60
  • 6
    Accessing properties or methods with `_` in the beginning is not the ideal solution. Using lean might solve your case. – Arthur Costa Sep 04 '19 at 06:02
  • @ArthurBrito yes. Its not a ideal solution but its still a SOLUTION! I have used `_doc` property many times without facing any issue. And using `lean` method gives you plain object on which you can't perform mongoose model operations. – dd619 Sep 05 '19 at 07:13
7

The lean option tells Mongoose to skip hydrating the result documents. This makes queries faster and less memory intensive, but the result documents are plain old JavaScript objects (POJOs), not Mongoose documents.

const leanDoc = await MyModel.findOne().lean();

not necessary to use JSON.parse() method

Amir BenAmara
  • 676
  • 7
  • 12
5

You can also stringify the object and then again parse to make the normal object. For example like:-

const obj = JSON.parse(JSON.stringify(mongoObj))
kaushik_pm
  • 295
  • 3
  • 10
1

I have been using the toObject method on my document without success. I needed to add the flattenMap property to true to finally have a POJO.

const data = document.data.toObject({ flattenMaps: true });
Cizia
  • 428
  • 5
  • 11