Hi. I have the following schema:
const messageSchema = mongoose.Schema({
content: String,
recipient: {
location: {
address: String,
},
},
});
export default mongoose.model('Message', messageSchema);
I later save messages by:
const messages = await Message.create([{content: 'some content', recipient: undefined }]);
In the DB, the message is saved without a recipient, as expected. However when retrieving the model, recipient is { location: {} }
.
As follows:
// GOOD
message.toObject().recipient; // is undefined -> GOOD!
// BAD
message.recipient; // is { location: {} } -> BAD!
// GOOD
(await Message.findOne({}).lean().exec()).recipient; // is undefined -> GOOD!
// BAD
(await Message.findOne({}).exec()).recipient; // is { location: {} } -> BAD!
I expected all the above to have an undefined recipient. Was I wrong?
I tried to set the Schema as minimize: false but it only makes the empty object to be also saved to the DB.
minimize: true
is the way to go if you don't want the empty objects to show up in the database or in JSON.stringify()
.
Unfortunately, this is behavior that mongoose needs. In order to support change detection and getters/setters on nested properties, mongoose does Object.defineProperty()
to create nested properties like recipient
and recipient.location
on the Message
model's prototype. This is because Object.defineProperty()
is prohibitively slow, so doing it every time a document is created would increase mongoose's performance overhead by orders of magnitude. This means that message.recipient
must always be an object, because message.recipient.location
must be defined via defineProperty()
on the prototype.
We could hack it so message.recipient
is an object whose valueOf()
is undefined, so it would look undefined
when you console.log()
it. But, because of how JS ==
is defined, an object whose valueOf()
returns undefined is neither falsy nor nullish, so message.recipient
would print out as undefined but !message.recipient
and message.recipient == null
would both be false. This would be very confusing, even caused me a hefty bit of headache while debugging.
I'll add this to the FAQ. Is there a specific problem you're having with this behavior that I can help out with?
Thanks Valeri for the detailed answer !
I understand the problem. I intended to rely on whether recipient is null or not in my logic, but I can live with a boolean flag as well. Less pretty, IMO, but works.
Thanks again!
I added a new issue :point_up: for an isEmpty()
helper for this case.
2 quick questions on this:
1) minimize: true
won't save anything in the DB, but is there a way to get this null
behaviour when loading an object? I.e. if recipient
is null in the DB, then it'll be null
when loaded using Mongoose? I don't really need change detection etc.
2) Is there a doc somewhere showing how isEmpty()
is meant to be used in the meantime? In the issue above, it's not in the code sample, and I couldn't find it in the API docs here: http://mongoosejs.com/docs/api.html
Thanks
Use lean()
http://mongoosejs.com/docs/api.html#query_Query-lean
We haven't implemented isEmpty()
yet (follow #5369 for updates), but ideally you would do message.recipient.isEmpty()
instead of message.recipient == null
.
I found a better way to solve this problem...
I had the following Schema:
const newSchema = new Schema = ({
status: {
type: String,
required: true,
default: "default"
},
optionalObject: {
conditions: {
all: [{
type: String
}],
},
event: {
type: {
type: String,
},
},
}
})
And I wanted optionalObject
return null
if it was not saved instead it returned
optionalObject: { conditions: { all: [] } }
the solution was to make the optionalObject it's own schema as follows:
const optionalObject = new Schema({
optionalObject: {
conditions: {
all: [{
type: String
}],
},
event: {
type: {
type: String,
},
},
}
});
const newSchema = new Schema = ({
status: {
type: String,
required: true,
default: "default"
},
optionalObject: { type: optionalObject, default: null },
})
This ensures that if optionalObject
is not saved in the database it will be null and not an empty object
Most helpful comment
Use
lean()
http://mongoosejs.com/docs/api.html#query_Query-leanWe haven't implemented
isEmpty()
yet (follow #5369 for updates), but ideally you would domessage.recipient.isEmpty()
instead ofmessage.recipient == null
.