Mongoose supports two Schema options to transform Objects after querying MongoDb: toObject
and toJSON
.
In general you can access the returned object in the transform
method toObject
or toJSON
as described in the docs.
Where things get interesting is when you're trying to use sub-documents and want them to be not touched by the transform method of the root or parent document.
After playing around with toObject
and toJSON
transforms with sub-documents, I observed the behaviors described as follows.
First, our two schemas for context:
User.js:
'use strict';
const mongoose = require('mongoose');
let UserSchema = new mongoose.Schema({
name: String
});
module.exports = mongoose.model("User", UserSchema);
Post.js:
'use strict'
let mongoose = require('mongoose');
let PostSchema = new mongoose.Schema({
title: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
comments: [{
text: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}]
}, {
toObject: {
transform: function (doc, ret) {
delete ret._id;
}
},
toJSON: {
transform: function (doc, ret) {
delete ret._id;
}
}
});
module.exports = mongoose.model("Post", PostSchema);
As you can see, mongoose.Schema
accepts a second parameter which contains the definitions for toObject
and toJSON
.
Next, let's use both Schemas in simple sample:
app.js
'use strict';
require("./database");
let User = require('./User'),
Post = require('./Post');
let alex = new User({
name: "Alex"
});
let joe = new User({
name: "Joe"
});
alex.save();
joe.save();
let post = new Post({
title: "Hello World",
postedBy: alex._id,
comments: [{
text: "Nice post!",
postedBy: joe._id
}, {
text: "Thanks :)",
postedBy: alex._id
}]
});
post.save(function (error) {
if (!error) {
Post.find({})
.populate('postedBy')
.populate('comments.postedBy')
.exec(function (error, posts) {
console.log(posts)
})
}
});
The result is as follows:
[ { comments: [ [Object], [Object] ],
__v: 0,
postedBy: { __v: 0, name: 'Alex' },
title: 'Hello World' } ]
As you can see, _id
from both Post
and User
get removed by the toObject
transformation.
Next, we'll replace console.log(posts)
as follwos:
console.log(JSON.stringify(posts, null, '\t'));
The result is the following JSON:
[
{
"title": "Hello World",
"postedBy": {
"_id": "57588aa352559c927c98c793",
"name": "Alex",
"__v": 0
},
"__v": 0,
"comments": [
{
"text": "Nice post!",
"postedBy": {
"_id": "57588aa352559c927c98c794",
"name": "Joe",
"__v": 0
},
"_id": "57588aa352559c927c98c797"
},
{
"text": "Thanks :)",
"postedBy": {
"_id": "57588aa352559c927c98c793",
"name": "Alex",
"__v": 0
},
"_id": "57588aa352559c927c98c796"
}
]
}
]
The _id
from Post
gets removed while _id
from User
remains.
The same behavior can be seen when we call toJSON
explicitly:
let json = posts.map(function (p) {
return p.toJSON()
});
console.log(json)
Result:
[ { title: 'Hello World',
postedBy: { _id: 57588c7ebf8340cc859660e1, name: 'Alex', __v: 0 },
__v: 0,
comments: [ [Object], [Object] ] },
{ title: 'Hello World',
postedBy: { _id: 57588c97fc8232548659e6d4, name: 'Alex', __v: 0 },
__v: 0,
comments: [ [Object], [Object] ] } ]
So it looks like toObject
is being applied to sub-documents while toJSON
is not. And this is different from what the documentation says:
Transforms are applied only to the document and are not applied to sub-documents.
The other option is that I'm wrong but questions on stackoverflow show it really might be the documentation.