Search code examples
node.jsmongodbmongoosemongoose-schema

mongoose schema transform not invoked if document is returned directly from query


I have an endpoint that does an operation such as this:

        const pipeline = [
          {
            $match: {
              $and: [
                {
                  $or: [...],
                },
              ],
            },
          },
          {
            $group: {
              _id : '$someProp',
              anotherProp: { $push: '$$ROOT' },
            },
          },
          { $sort: { date: -1 } },
          { $limit: 10 },
        ]
        const groupedDocs = await MyModel.aggregate(pipeline);

The idea here is that the returned documents look like this:

[
  {
    _id: 'some value',
    anotherProp: [ /* ... array of documents where "someProp" === "some value" */ ],
  },
  {
    _id: 'another value',
    anotherProp: [ /* ... array of documents where "someProp" === "another value" */ ],
  },
  ...
]

After getting these results, the endpoint responds with an array containing all the members of anotherProp, like this:

const response = groupedDocs.reduce((docs, group) => docs.concat(group.anotherProp), []);
res.status(200).json(response);

My problem is that the final documents in the response contain the _id field, but I want to rename that field to id. This question addresses this issue, and specifically this answer is what should work, but for some reason the transform function doesn't get invoked. To put it differently, I've tried doing this:

schema.set('toJSON', {
    virtuals: true,
    transform: function (doc, ret) {
        console.log(`transforming toJSON for document ${doc._id}`);
        delete ret._id;
    },
});
schema.set('toObject', {
    virtuals: true,
    transform: function (doc, ret) {
        console.log(`transforming toObject for document ${doc._id}`);
        delete ret._id;
    },
});

But the console.log statements are not executed, meaning that the transform function is not getting invoked. So I still get the _id in the response instead of id.

So my question is how can I get id instead of _id in this scenario?

Worth mentioning that toJSON and toObject are invoked (the console.logs show) in other places where I read properties from the documents. Like if I do:

const doc = await MyModel.findById('someId');
const name = doc.name;
res.status(200).json(doc);

The response contains id instead of _id. It's almost like the transform function is invoked once I do anything with the documents, but if I pass the documents directly as they arrive from the database, neither toJSON nor toObject is invoked.

Thanks in advance for your insights. :)


Solution

  • The toJSON and toObject methods won't work here because they don't apply to documents from an aggregation pipeline. Mongoose doesn't convert aggregation docs to mongoose docs, it returns the raw objects returned by the pipeline operation. I ultimately achieved this by adding pipeline stages to first add an id field with the same value as the _id field, then a second stage to remove the _id field. So essentially my pipeline became:

            const pipeline = [
              {
                $match: {
                  $and: [
                    {
                      $or: [...],
                    },
                  ],
                },
              },
              // change the "_id" to "id"
              { $addFields: { id: '$_id' } },
              { $unset: ['_id'] },
              {
                $group: {
                  _id : '$someProp',
                  anotherProp: { $push: '$$ROOT' },
                },
              },
              { $sort: { date: -1 } },
              { $limit: 10 },
            ]
            const groupedDocs = await MyModel.aggregate(pipeline);