user2234151
user2234151

Reputation: 131

MongoDB Aggregation Framework - Dynamic Field Rename

I am finding the MongoDB aggregation framework to be extremely powerful - it seems like a good option to flatten out an object. My schema uses a an array of sub objects in an array called materials. The number of materials is variable, but a specific field category will be unique across objects in the array. I would like to use the aggregation framework to flatten the structure and dynamically rename the fields based on the value of the category field. I could not find an easy way to accomplish this using a $project along with $cond. Is there a way?

The reason for the array of material objects is to allow simple searching:

e.g. { 'materials.name' : 'XYZ' } pulls back any document where "XYZ" is found.

E.g. of before and after document

{
"_id" : ObjectId("123456"),
"materials" : [
    {
        "name" : "XYZ",
        "type" : "Red",
        ...
        "category" : "A"
    },
    {
        "name" : "ZYX",
        "type" : "Blue",
        ...
        "category" : "B"
    }]
}

to

{
"material_A_name" : "XYZ",
"material_A_type" : "Red",
...
"material_B_name" : "ZYX",
"material_B_type" : "Blue",
...
}

Upvotes: 8

Views: 5633

Answers (2)

turivishal
turivishal

Reputation: 36154

This would be useful from MongoDB v4.4,

  • $map to iterate loop of materials array
  • $map to iterate loop of name and type fields after converting to array using $objectToArray, concat your key fields requirement as per fields and value using $concat,
  • back to first $map convert returned result from second $map from array to object using $arrayToObject
  • $unwind deconstruct materials array
  • $group by null and merge materials object to one object
  • $replaceRoot to replace object in root
db.collection.aggregate([
  {
    $project: {
      materials: {
        $map: {
          input: "$materials",
          as: "m",
          in: {
            $arrayToObject: [
              {
                $map: {
                  input: {
                    $objectToArray: {
                      name: "$$m.name",
                      type: "$$m.type"
                    }
                  },
                  in: {
                    k: { $concat: ["material", "_", "$$m.category", "_", "$$this.k"] },
                    v: "$$this.v"
                  }
                }
              }
            ]
          }
        }
      }
    }
  },
  { $unwind: "$materials" },
  {
    $group: {
      _id: null,
      materials: { $mergeObjects: "$materials" }
    }
  },
  { $replaceRoot: { newRoot: "$materials" } }
])

Playground

Upvotes: 3

Asya Kamsky
Asya Kamsky

Reputation: 42362

There is a request for something like this in jira https://jira.mongodb.org/browse/SERVER-5947 - vote it up if you would like to have this feature.

Meanwhile, there is a work-around if you know up front what the possible values of the keys will be (i.e. all unique values of "category") and I have some sample code on it on my blog.

Upvotes: 6

Related Questions