Search code examples
mongodbmongoosemongodb-queryaggregation-framework

See if nested array of objects contains value | MongoDB


I have a collection posts which has an array of objects comments. Within this array of objects, I have another array of objects likes.

I am trying to write a query that pulls the most recent 5 comments from a post, and only pulls true or false for likes, based on if the user has liked the comment already.

I have written this so far:

db.posts.aggregate([
    {
        "$match": {
            "_id": postId
        }
    },
    {
        "$project": 
        {
            "comments": {
                "$slice": [ "$comments", -5 ]
            }
        }
    },
    {
        "$project": {
            "comments.content": 1,
            "comments.likes": { 
                "$eq":  [ "comments.likes.$.createdBy.username", username ] 
            } 
        }
    }
])

But this seems to pull false everytime.

Is it possible to do this without having to write a separate query to check if the user has liked?

EDIT: So for the below document: Test

With username = "testusername", and postId = "60fcd335abbe5a73583b69f0"

I would expect output:

[
    {
        "content": "test comment",
        "likes": true
    },
    {
        "content": "another test comment",
        "likes": true
    }
]

And with username = "testusername2" I would expect output

[
    {
        "content": "test comment",
        "likes": true
    },
    {
        "content": "another test comment",
        "likes": false
    }
]

Answer

Thanks to @ray for your help with this one. Condensed Code Here though please see ray's response for the code split out with explanation.


Solution

  • You can use $map to process your arrays layer-by-layer.

    1. You can first $map comment to project a boolean for if the likes are liked by user1
    2. Then you can use $anyElementTrue to perform the checking on the projected boolean
    db.posts.aggregate([
      {
        "$match": {
          "_id": "p1"
        }
      },
      {
        "$project": {
          "comments": {
            "$slice": [
              "$comments",
              -5
            ]
          }
        }
      },
      {
        "$project": {
          likes: {
            "$map": {
              "input": "$comments",
              "as": "c",
              "in": {
                content: "$$c.content",
                likedByU1: {
                  "$map": {
                    "input": "$$c.likes",
                    "as": "l",
                    "in": {
                      $eq: [
                        "$$l.createdBy._id",
                        "u1"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      {
        "$project": {
          likes: {
            "$map": {
              "input": "$likes",
              "as": "l",
              "in": {
                content: "$$l.content",
                likedByU1: {
                  "$anyElementTrue": [
                    "$$l.likedByU1"
                  ]
                }
              }
            }
          }
        }
      }
    ])
    

    Here is the Mongo playground to show the idea(with some minor modifications to your example). You can modify it to fit your needs.