'How to transform a list of objects into an outer new object of objects?

Given this collection of courses, there is a list of subjects that are composed of objects. Is it possible to create an aggregation to create a new field newSubjects that maps all subjects by their ids? For example, given this collection how to transform the subjects entries from :

[
  {
    _id: ObjectId("623e2f0cb242ee9367eb3c9f"),
    subjects: [ { id: '1', name: 'math' }, { id: '2', name: 'physics' } ]
  },
  {
    _id: ObjectId("623e2f17b242ee9367eb3ca0"),
    subjects: [ { id: '1', name: 'math' }, { id: '3', name: 'biology' } ]
  }
]

into:

{
  newSubjects: { '1': { id: '1', name: 'math' }, '2': { id: '2', name: 'physics' } }
}
{
  newSubjects: { '1': { id: '1', name: 'math' }, '3': { id: '3', name: 'biology' } }
}

Using JavaScript I could do with this expression:

db.courses.find().forEach(x => print({"newSubjects": Object.fromEntries(new Map(x.subjects.map(s => [s.id, s])))}))

I'm trying to figure out an aggregation to accomplish this but haven't succeeded with $addField and $map yet.



Solution 1:[1]

You can try this query:

Only need one aggregate stage to compound the result you want. In this stage you can use a $map to iterate for every value and return a new one. This new one will have k and v fields.

  • k field will be the object key (1, 2, 3...)
  • v field will be the value field (the object with {id:..., name:...})

And wrapped by $arrayToObject to translate k and v to the desired object.

db.collection.aggregate([
  {
    "$project": {
      "_id": 0,
      "newSubjects": {
        "$arrayToObject": {
          "$map": {
            "input": "$$ROOT.subjects",
            "in": {
              "k": "$$this.id",
              "v": {
                "id": "$$this.id",
                "name": "$$this.name"
              }
            }
          }
        }
      }
    }
  }
])

Example here

Solution 2:[2]

Another option:

 db.collection.aggregate([
{
"$project": {
  "newSubjects": {
    "$arrayToObject": {
      "$map": {
        "input": "$subjects",
        "as": "s",
        "in": {
          k: "$$s.id",
          v: "$$s"
        }
       }
      }
     }
    }
   }
 ])

Explained:

Map the array with the necessary k,v suitable for arrayToObject to be projected as newSubjects

playground

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 J.F.
Solution 2 R2D2