skip to Main Content

Im writing a mongodb aggregate pipeline.
My documents are of the form below :

   {
  name: "01"
  tags {
  tag_01: {
    a: "a01-val",
    b: "b01-val"
  },
  tag_02: {
    a: "a02-val",
    b: "b02-val"
  }
  }
},
{
  name: "02"
  tags {
  tag_03: {
    a: "a03-val",
    b: "b03-val"
  },
  tag_04: {
    a: "a04-val",
    b: "b04-val"
  }
  }
}

My pipeline should generate documents as below :

{
  name: "01"
  tags {
  tag_01: {
    a: "a01-val",
    b: "b01-val",
    tagName: "tag_01"
  },
  tag_02: {
    a: "a02-val",
    b: "b02-val",
    tagName: "tag_02"
  }
  }
},
{
  name: "02"
  tags {
  tag_03: {
    a: "a03-val",
    b: "b03-val",
    tagName: "tag_03"
  },
  tag_04: {
    a: "a04-val",
    b: "b04-val",
    tagName: "tag_04"
  }
  }
}

In other words, a new field is added inside each subdocument whose value is the name of the parent field.
Is it possible to do this?

2

Answers


  1. Use $objectToArray to convert tags into an array of k-v tuples. Use $map to iterate through the array elements and $mergeObjects to set the field with k. Then use $arrayToObject to revert back to original object form.

    db.collection.update({},
    [
      {
        "$set": {
          "tags": {
            "$objectToArray": "$tags"
          }
        }
      },
      {
        "$set": {
          "tags": {
            "$map": {
              "input": "$tags",
              "as": "tag",
              "in": {
                k: "$$tag.k",
                v: {
                  "$mergeObjects": [
                    "$$tag.v",
                    {
                      "tagName": "$$tag.k"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      {
        $set: {
          tags: {
            "$arrayToObject": "$tags"
          }
        }
      }
    ],
    {
      multi: true
    })
    

    Mongo Playground

    Login or Signup to reply.
    • Start with $objectToArray on tags, then unwind it to get one record/document per tag / k-v tuples.
    • Set the tagName field in each of those v objects, using the value of k in each of the unwound tags.
    • Group them back together using $push to get an array of the previously unwound k-v objects.
      • If you have other fields, use fieldName: { $first: "$fieldName" } to add them or use $mergeObjects with $$ROOT, depending on how many of them/ease of writing.
    • And then use $arrayToObject to get tags in the original form.
    db.collection.aggregate([
      {
        $set: {
          tags: { $objectToArray: "$tags" }
        }
      },
      { $unwind: "$tags" },
      {
        $set: {
          tags: { v: { tagName: "$tags.k" } }
        }
      },
      {
        $group: {
          _id: "$_id",
          name: { $first: "$name" },
          tags: { $push: "$tags" }
        }
      },
      {
        $set: {
          tags: { $arrayToObject: "$tags" }
        }
      }
    ])
    

    Mongo Playground

    PS. For performance, you’re probably better off with ray’s solution.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search