How to get objects inside an array of objects that are inside a document in mongoose / mongodb?
Here is the structure of the document.
{
"_id" : ObjectId("5548b2b79b9567341c77d352"),
"messages" : [
{
"subject" : "fresh subject ",
"from" : ObjectId("5534b2992a104ed914435c31"),
"_id" : ObjectId("5548b5dab9279faf1c1b8688"),
"created" : ISODate("2015-05-05T12:21:46.261Z"),
"read" : true,
"message" : "fresh message ",
"participants" : [
ObjectId("5534b2992a104ed914435c31"), //logged in user
ObjectId("5530af38576214dd3553331c")
]
},
{
"subject" : " subjet",
"from" : ObjectId("5530af38576214dd3553331c"),
"_id" : ObjectId("5548b608b9279faf1c1b8689"),
"created" : ISODate("2015-05-05T12:22:32.809Z"),
"read" : true,
"message" : "is fresh?",
"participants" : [
ObjectId("5530af38576214dd3553331c")
]
}
],
"participants" : [
ObjectId("5534b2992a104ed914435c31"),
ObjectId("5530af38576214dd3553331c")
],
"__v" : 2
}
There are several objects in the message array of the perticular element. I only want to get objects inside the post array if the member array of that object contains the logged in user.
I have a Document Object ID (5548b2b79b9567341c77d352) and I have a registered User ID (5534b2992a104ed914435c31). How do you do the same in a mongoose?
source to share
You can use MongoDB aggregation framework to get the desired result. The aggregation pipeline will consist of an initial step that has a statement that filters documents based on the criteria above. The next step in the pipeline would be an operator that deconstructs the message array from the input documents to output the document for each item. The following filter only returns documents with member ID. The last step with the operator then passes over documents with only the specified fields: $match
$unwind
$match
$project
db.collection.aggregate([
{
"$match": {
"_id" : ObjectId("5548b2b79b9567341c77d352"),
"messages.participants": ObjectId("5534b2992a104ed914435c31")
}
},
{
"$unwind": "$messages"
},
{
"$match": {
"messages.participants": ObjectId("5534b2992a104ed914435c31")
}
},
{
"$project": {
"_id": 0,
"messages": 1
}
}
])
Result
/* 0 */
{
"result" : [
{
"messages" : {
"subject" : "fresh subject ",
"from" : ObjectId("5534b2992a104ed914435c31"),
"_id" : ObjectId("5548b5dab9279faf1c1b8688"),
"created" : ISODate("2015-05-05T12:21:46.261Z"),
"read" : true,
"message" : "fresh message ",
"participants" : [
ObjectId("5534b2992a104ed914435c31"),
ObjectId("5530af38576214dd3553331c")
]
}
}
],
"ok" : 1
}
In Mongoose, you can use the same aggregation as below:
// Using the pipeline builder
Model.aggregate({
"$match: {
""_id" : ObjectId("5548b2b79b9567341c77d352"),
"messages.participants": ObjectId("5534b2992a104ed914435c31")
}
})
.unwind("messages")
.match({ "messages.participants": ObjectId("5534b2992a104ed914435c31") })
.project({
"_id": 0,
"messages": 1
})
.exec(function (err, res) {
if (err) return handleError(err);
console.log(res);
});
// Or the simple aggregate method
var pipeline = [
{
"$match": {
"_id" : ObjectId("5548b2b79b9567341c77d352"),
"messages.participants": ObjectId("5534b2992a104ed914435c31")
}
},
{
"$unwind": "$messages"
},
{
"$match": {
"messages.participants": ObjectId("5534b2992a104ed914435c31")
}
},
{
"$project": {
"_id": 0,
"messages": 1
}
}
]
Model.aggregate(pipeline, function (err, res) {
if (err) return handleError(err);
console.log(res);
});
source to share
Another solution besides the aggregation structure is to use the projection operator $ elemMatch .
For example:
var id = '5548b2b79b9567341c77d352';
var loggedInUserId = '5534b2992a104ed914435c31';
var projection = {
participants: 1,
messages: {
$elemMatch: { participants: new ObjectId(loggedInUserId) }
}
};
Model.findById(id, projection, function(err, result) {
console.log(result);
});
This will output:
{ _id: 5548b2b79b9567341c77d352,
participants: [ 5534b2992a104ed914435c31, 5530af38576214dd3553331c ],
messages:
[ { participants: [Object],
message: 'fresh message ',
read: true,
created: Tue May 05 2015 09:21:46 GMT-0300 (BRT),
_id: 5548b5dab9279faf1c1b8688,
from: 5534b2992a104ed914435c31,
subject: 'fresh subject ' } ] }
source to share
You can use Aggregate structure for $unwind
arrays of subdocuments (splits them into your own documents that you can match against). I would use the following query:
.aggregate([
{$unwind: "$messages"},
{$match:
{"messages.participants": ObjectId("5534b2992a104ed914435c31")}
}
])
This will return a separate document message
in the result set for each subdocument message
that matches this member.
Using Mongoose constructs it would look something like this:
Conversation
.aggregate({$unwind: "messages"})
.match({
"messages.participants": mongoose.Types.ObjectId("5534b2992a104ed914435c31")
})
.exec(cb);
The first is $match
not strictly necessary, but
source to share