Search code examples
arraysmongodbaggregate

$arrayToObject with duplicated key


considering the following document

    "logs": {
      "events": {
        "type": "call_action",
        "attributes": [
          {
            "key": "module",
            "value": "module1"
          },
          {
            "key": "data",
            "value": "data_value_module_1"
          },
          {
            "key": "module",
            "value": "module2"
          },
          {
            "key": "data",
            "value": "data_value_module_2"
          }
        ]
      }
    },

I am trying to use $arraytoObject to convert key and value

$project: {
  "attributes": {
    $arrayToObject: {
      $map: {
        "input": "$logs.events.attributes",
        "as": "el",
        "in": {
          "k": "$$el.key",
          "v": "$$el"
        }
      }
    }
  }
}

but it overwrite duplicated keys as module and data.

How can I use a kind of $unwind or something like that?

The result should be like that

[
  {
    "logs": {
      "events": {
        "type": "call_action",
        "attributes": {
          "module": "module1"
          "value": "data_value_module_1"
        }
      }
    }
  },
  {
    "logs": {
      "events": {
        "type": "call_action",
        "attributes": {
          "module": "module2"
          "value": "data_value_module_2"
        }
      }
    }
  }
]

Thank you


Solution

  • Based on this answer, one option is:

    db.collection.aggregate([
      {$set: {
          "logs.events.attributes": {
            $reduce: {
              input: "$logs.events.attributes",
              initialValue: [],
              in: {
                $concatArrays: [
                  "$$value",
                  [
                    {
                      k: "$$this.key",
                      v: "$$this.value",
                      mod: {$mod: [{$size: "$$value"}, 2]}
                    }
                  ]
                ]
              }
            }
          }
        }
      },
      {$project: {
          _id: 0,
          type: "$logs.events.type",
          firstEvent: {
            $filter: {
              input: "$logs.events.attributes",
              cond: {$eq: ["$$this.mod", 0]}
            }
          },
          secondEvent: {
            $filter: {
              input: "$logs.events.attributes",
              cond: {$eq: ["$$this.mod", 1]}
            }
          }
        }
      },
      {$project: {
          "logs.events": {
            type: "$type",
            attributes: {$zip: {inputs: ["$firstEvent", "$secondEvent"]}}
          }
        }
      },
      {$unwind: "$logs.events.attributes"},
      {$set: {
          "logs.events.attributes": {
            $map: {
              input: "$logs.events.attributes",
              in: {k: "$$this.k", v: "$$this.v"}}
          }
        }
      },
      {$set: {"logs.events.attributes": {$arrayToObject: $logs.events.attributes"}}}
    ])
    

    See how it works on the playground example

    Or with a small variation (I generally prefer not to $unwind and $group again, but in this case it seems reasonable):

    db.collection.aggregate([
      {$set: {
          "logs.events.attributes": {
            $reduce: {input: "$logs.events.attributes",
              initialValue: [],
              in: {$concatArrays: [
                  "$$value",
                  [
                    {
                      k: "$$this.key",
                      v: "$$this.value",
                      group: {$floor: [{$divide: [{$size: "$$value"}, 2]}]}
                    }
                  ]
                ]
              }
            }
          }
        }
      },
      {$unwind: "$logs.events.attributes"},
      {$group: {
          _id: "$logs.events.attributes.group",
          data: {$push: {k: "$logs.events.attributes.k", v: "$logs.events.attributes.v"}},
          type: {$first: "$logs.events.type"}
        }
      },
      {$project: {_id: 0, "logs.events": {attributes: {$arrayToObject: "$data" }, type: "$type"}}}
    ])
    

    See how it works on the playground example - simplified