Search code examples
mongodbmongooseaggregate

Combining arrays within single mongo document with aggregate


I am trying to create a call that combines all images between 'images' and 'posts.images' and sorts by createdAt. So far I have:

output = [Array(1), Array(3), {…}, {…}, {…}, {…}, {…}, {…}]

So it appears to be combining the arrays into a parent array instead of merging into a single array.

What I want:

output = [{…},{…},{…},{…},{…},{…},{…},{…},{…},{…},{…},{…}]

Here is my schema:

const deviceSchema = new Schema({
owner: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
},
images: [{
    url: {
        type: String,
    },
    createdAt: {
        type: Date,
    }
}],
posts: [{
    description: {
        type: String,
    },
    images: [{
        url: {
            type: String,
        },
        createdAt: {
            type: Date,
        }
    }],
    createdAt: {
        type: Date,
    }
}],
});

Here is my aggregate call:

const images = await Device.aggregate([
        { $project: {
            combinedImages: { $concatArrays: ["$images", "$posts.images"] }
        }},
        { $match: { _id: id }},
        { $unwind: '$combinedImages' },
        { $sort: { 'combinedImages.createdAt': -1 }},
        { $skip: (page-1)*limit },
        { $limit: limit },
        { $group: { _id: '$_id', images: { $push: '$combinedImages'}}}
    ])

Solution

  • You can use a combination of $concatArrays and $reduce to combine all images into a single array, e.g.:

    db.collection.aggregate([
      {
        $set: {
          combinedImages: {
            "$concatArrays": [
              "$images",
              {
                $reduce: {
                  input: "$posts",
                  initialValue: [],
                  in: {
                    "$concatArrays": [
                      "$$value",
                      "$$this.images"
                    ]
                  }
                }
              }
            ]
          }
        }
      }
    ])
    

    This leads to the following output:

    {
        // ...
        "combinedImages": [
          {
            "url": "img1"
          },
          {
            "url": "img2"
          },
          {
            "url": "post_img11"
          },
          {
            "url": "post_img12"
          },
          {
            "url": "post_img21"
          },
          {
            "url": "post_img22"
          }
        ],
        // ...
    }
    

    See this mongoplayground to test.