Mongoose: How to turn a document into a plain JS object

Updated: December 30, 2023 By: Guest Contributor Post a comment

Introduction

This tutorial guides you through the process of transforming Mongoose documents to plain JavaScript objects. When working with Mongoose, the ODM (Object Document Mapper) for MongoDB, the records retrieved from the database are instances of Mongoose documents. While these document instances have a lot of convenient methods, in some scenarios you might need to work with a normalized/plain JavaScript object (POJO). We are going to explore multiple ways of converting these documents with the help of Mongoose’s built-in functionality, paying close attention to the latest ES6 syntax.

This tutorial assumes a good understanding of how to work with MongoDB using Mongoose and the significance of having plain objects in JavaScript.

Basic Usage of toObject)

The simplest way to turn a Mongoose document into a plain JS object is to use the toObject() method that comes with every Mongoose document. The method returns a pure JavaScript object without any Mongoose specific properties or methods.

const doc = await SomeModel.findOne(); // Mongoose document instance
const plainObj = doc.toObject(); 

This is useful for when you need to modify the attributes of the object or want to send a clean JSON object to the client-side without any additional overhead.

Customizing toObject() Transformations

Sometimes, you might want to tweak the conversion when using toObject(). For instance, you might want to exclude certain fields or apply some transformation to the fields’ values. You can specify these customizations in the schema definition.

const userSchema = new mongoose.Schema({
    username: String,
    hidden: Boolean
  }, {
    toObject: {
      transform: (doc, ret) => {
        delete ret._id;
        delete ret.__v;
        delete ret.hidden;
        return ret;
      }
    }
  });

const User = mongoose.model('User', userSchema);

const doc = await User.findOne();
const plainObj = doc.toObject();

This custom transformation will remove the fields _id, __v, and hidden from the converted object.

Working with Lean Queries

Another way to skip the conversion altogether is by making use of Mongoose’s lean() method on queries. By calling this method, Mongoose will skip materializing documents and instead, return plain JavaScript objects directly from the database.

const plainObj = await SomeModel.findOne().lean();

This becomes a performance optimization as well, since skipping the instantiation of a full Mongoose document can be beneficial, especially when processing or retrieving large volumes of data.

Converting Nested Sub-Documents

Converting documents that contain nested sub-documents may require an additional step. You will need to ensure that each nested sub-document is also converted.


   const parentDoc = await ParentModel.findOne().populate('child');
   const plainObj = parentDoc.toObject({
     transform: (doc, ret) => {
       ret.child = ret.child.toObject();
       return ret;
     }
   });
 

This code assumes a one-to-one relationship where ‘child’ is a sub-document. The toObject() method is individually called on the nested sub-document as well.

Advanced: Avoiding Populated Fields Serialization

To serialize documents while avoiding fields that were dynamically populated (using Mongoose’s populate() method), the transform option comes handy once more. You can examine the document’s populated paths and decide what to do with each one.


   const document = await ParentModel.findOne().populate('children');
   const publicDocument = document.toObject({
     transform: (doc, ret, options) => {
       if (doc.populated('children')){
         delete ret.children;
       }
       return ret;
     }
   });
 

This ensures the ‘children’ path is not included in the resulting POJO if it was populated.

Using The JSON.stringify() Method

An alternative approach to get a plain object is to leverage JSON.stringify() along with JSON.parse(). This is a broader solution for any JavaScript object serialization. Be aware, however, that this method cannot address transformation needs and might not respect schema settings or custom toObject() methods.


   const doc = await SomeModel.findOne(); // Mongoose document instance
   const json = JSON.stringify(doc);
   const plainObj = JSON.parse(json);
 

The object returned is now stripped of Mongoose-specific properties, functions, and behaviors, leaving a pure JSON object.

Correctly Handling Virtuals

Handling virtuals deserves attention in the transformation process. Virtuals are properties that you can get and set but that do not get written to the database. They are usually used for computed properties on documents. Here’s how to include them in the toObject() output.

const userSchema = new mongoose.Schema({
       firstname: String,
       lastname: String
   });
   userSchema.virtual('fullname').get(function () {
     return this.firstname + ' ' + this.lastname;
   });

userSchema.set('toObject', { virtuals: true });

const User = mongoose.model('User', userSchema);
const userDoc = await User.findOne();
const plainObject = userDoc.toObject();

The fullname virtual will now be included in the resulting object because the toObject() option for virtuals is set to true.

Conclusion

This tutorial has provided a comprehensive guide to converting Mongoose documents into plain JavaScript objects. Starting from basic uses of the toObject() method, we’ve covered the lean() method, customized transformations, rational serialization with the use of JSON.stringify() and JSON.parse(), handling of nested sub-documents, as well as populated fields and virtuals. The choice of technique boils down to your specific application’s needs, and it’s crucial to consider performance implications. Remember that plain objects can be easier to manage and manipulate, thus reducing the complexity in certain parts of your code. Finally, always keep the Mongoose and Node.js documentation close at hand for the latest up-to-date features and the best practices.