Mongoose - remove _id from lean ( using toJSON() ?)

Viewed 937

I'm having a NODE.JS project using mongoose 5.x

My model have toJSON method which removes the _id & __v fields perfectly

mySchema.method("toJSON", function toJSON() {
  const {__v, _id, ...object} = this.toObject();
  return {
    id: _id,
    ...object
  };
});

so when fetching data from the db:

const data = myModel.findOne({_id: id});

I get an object that when serialized to the user:

res.json(data);

It doesn't contain the _id and __v fields as required.

The problem is when I use lean():

const data = myModel.findOne({_id: id}).lean();

the data object contains those fields.

I can remove them manually when using lean

but I would prefer to find a way to sanitize the data object in both cases with the same mechanism.

any suggestions?

Thanks in advance.

3 Answers

Not sure if this is what you want but maybe:

const data = myModel.findOne({_id: id}).lean().then(res => {
   delete res._id
   return res
})

When JSON.stringify() is called on an object, a check if done if it has a property called toJSON. It's not specific to Mongoose, as it works on plain objects:

const myObj = {
  _id: 123,
  foo: 'bar',
  // `toJSON() {...}` is short for `toJSON: function toJSON() {...}`
  toJSON() {
    // Because "this" is used, you can't use an arrow function.
    delete this._id;
    return this;
  }
};
console.log(JSON.stringify(myObj));
// {"foo":"bar"}

Mongoose doesn't have an option to automatically inject a toJSON function into objects returned by lean(). But that's something you can add.

First, create a function that:

  1. Takes an object with properties
  2. Listens to when Mongoose runs a find query
  3. Tells Mongoose that after the query, it should change the result
  4. The change: merge the result with the object from step 1.
function mergeWithLeanObjects(props) {
  // Return a function that takes your schema.
  return function(schema) {
    // Before these methods are run on the schema, execute a function.
    schema.pre(['find', 'findOne'], function() {
      // Check for {lean:true}
      if (this._mongooseOptions.lean) {
        // Changes the document(s) that will be returned by the query.
        this.transform(function(res) {
          // [].concat(res) makes sure its an array.
          [].concat(res).forEach(obj => Object.assign(obj, props));
          return res;
        });
      }
    });
  };
}

Now add it to your schema:

mySchema = new mongoose.Schema({
  foo: String
});

mySchema.plugin(mergeWithLeanObjects({
  toJSON() {
    delete this._id;
    delete this.__v;
    return this;
  }
}));

Then test it:

const full = await myModel.findOne();
const lean = await myModel.findOne().lean();

console.log(full);
// Logs a Mongoose document, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"), foo: 'bar', __v: 1 }

console.log(JSON.stringify(full));
// Logs a JSON string, all properties.
{"_id":"62a8b39466768658e7333154","foo":"bar","__v":1}

console.log(lean);
// Logs an Object, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"),
  foo: 'bar', __v: 1, toJSON: [Function: toJSON] }

console.log(JSON.stringify(lean));
// Logs a JSON string, filtered properties.
{"foo":"bar"}

If you want to re-use the plugin with the same settings on multiple schemas, just save the function that mergeWithLeanObjects returns somewhere.

// file: clean.js
module.exports = mergeWithLeanObjects({
  toJSON() {
    delete this._id;
    delete this.__v;
    return this;
  }
});

// file: some-schema.js
schema1.plugin(require('./clean.js'));

// file: some-other-schema.js
schema2.plugin(require('./clean.js'));

There's also mongoose.plugin() to add the function to all schemas.

Try this to retrieve the _id from a document

myModel.findOne({_id: id}, function(err, doc) {
   if(err)
     return 'do something with this err'

   console.log(doc._id)
})
Related