Mongodb - Merge nested subdocuments array based on specific properties

Viewed 139

I've built an aggregation that is returning this data:

 [
      {
        "users": [
          {
            _id: 1,
            name: "John",
            age: 31
          },
          {
            _id: 2,
            name: "Jane",
            age: 26
          }
        ],
        "teams": [
          {
            id: 1,
            name: "Team 1",
            color: "yellow"
          },
          {
            id: 2,
            name: "Team 2",
            color: "red"
          }
        ],
        "moreTeams": [
          {
            id: 1,
            name: "Team 1 - More",
          },
          {
            id: 2,
            name: "Team 2 - More",
          },
          {
            id: 3,
            name: "Team 3 - More",
            extra: "extra"
          }
        ]
      }
    ]

How can I group "teams" and moreTeams into the same array (based on id), keeping all properties and eventually overriding where they have the same names?

Here's my desired result:

 [
          {
            "users": [
              {
                _id: 1,
                name: "John",
                age: 31
              },
              {
                _id: 2,
                name: "Jane",
                age: 26
              }
            ],
            "groupedTeams": [
              {
                id: 1,
                name: "Team 1 - More",
                color: "yellow"
              },
              {
                id: 2,
                name: "Team 2 - More",
                color: "red"
              },
              {
                id: 3,
                name: "Team 3 - More",
                extra: "extra"
              }
            ]
          }
        ]

I've tried using $unwind and $group with no success :(

Palyground example: https://mongoplayground.net/p/yZY8oQ9r-N1

1 Answers
  • $map to iterate loop of moreTeams array
  • $filter to iterate loop of teams array and filter matching object by id
  • $arrayElemAt to get first element object from above filter
  • $mergeObjects to merge above object from arrayElemAt and current object
db.collection.aggregate([
  {
    $addFields: {
      moreTeams: {
        $map: {
          input: "$moreTeams",
          as: "m",
          in: {
            $mergeObjects: [
              {
                $arrayElemAt: [
                  {
                    $filter: {
                      input: "$teams",
                      cond: { $eq: ["$$m.id", "$$this.id"] }
                    }
                  },
                  0
                ]
              },
              "$$m"
            ]
          }
        }
      }
    }
  },

Now we have merged moreTeams in teams but we have to merge teams in moreTeams as well

  • $project, $filter to iterate loop of teams array and match condition if id is not in moreTeams
  • $concatArrays to concat moreTeams and above filtered array that is not in moreTeams
  {
    $project: {
      users: 1,
      moreTeams: {
        $concatArrays: [
          "$moreTeams",
          {
            $filter: {
              input: "$teams",
              cond: { $not: { $in: ["$$this.id", "$moreTeams.id"] } }
            }
          }
        ]
      }
    }
  }
])

Playground

Related