Sling Academy
Home/Node.js/Mongoose: How to turn a document into a plain JS object

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

Last updated: December 30, 2023

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.

Next Article: Mongoose: How to safely change schema in production

Previous Article: Mongoose $match operator (with examples)

Series: Mongoose.js Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates