skip to Main Content

Trying to condense an array with key value pairs into an array of objects with the key and all the unique values for that key.

I have a structure like:

{
  fruits: [
    {fruit: apple, type: gaja},
    {fruit: apple, type: honey-crisp},
    {fruit: apple, type: fuji},
    {fruit: cherry, type: black},
    {fruit: cherry, type: red},
    {fruit: cherry, type: red},
  ]
}

How can I convert it to:

{
  fruits: [
    {fruit: apple, types: [gaja, honey-crisp, fuji]},
    {fruit: cherry, types: [black, red]}
  ]
}

Using mongo aggregations I managed to get the first structure from my data using $group and $addToSet. Not sure how to map the array to new object with a key and list of values

3

Answers


  1. Maybe something like this:

    db.collection.aggregate([
    {
      $unwind: "$fruits"
    },
    {
     $group: {
      _id: "$fruits.fruit",
      type: {
        $push: "$fruits.type"
      }
     }
    },
    {
     $project: {
      fruit: "$_id",
      type: 1,
      _id: 0
     }
    },
    {
    $group: {
      _id: "",
      fruits: {
        $push: "$$ROOT"
      }
     }
     }
    ])
    

    Explained:

    1. Unwind the array
    2. Group to form the type array ( you can use $push or $addToSet in case you need only unique )
    3. Project the necessary fields
    4. Group all documents inside single final one

    Playground

    Login or Signup to reply.
  2. Here’s another way to do it by using "$reduce". Comments are in the aggregation pipeline.

    db.collection.aggregate([
      {
        "$set": {
          // rewrite fruits
          "fruits": {
            "$reduce": {
              "input": "$fruits",
              "initialValue": [],
              "in": {
                "$let": {
                  "vars": {
                    // get fruit index in $$value : will be -1 if not there
                    "idx": {"$indexOfArray": ["$$value.fruit", "$$this.fruit"]}
                  },
                  "in": {
                    "$cond": [
                      // is fruit not in $$value yet
                      {"$eq": ["$$idx", -1]},
                      // new fruit so put in $$value and make "type" an array
                      {
                        "$concatArrays": [
                          "$$value",
                          [{"$mergeObjects": ["$$this", {"type": ["$$this.type"]}]}]
                        ]
                      },
                      // fruit already in $$value, so map $$value with "type" update
                      {
                        "$map": {
                          "input": "$$value",
                          "as": "val",
                          "in": {
                            "$cond": [
                              // is this array element not the right fruit?
                              {"$ne": ["$$val.fruit", "$$this.fruit"]},
                              // nope, leave the element as-is
                              "$$val",
                              // this element needs to be updated
                              {
                                "$mergeObjects": [
                                  "$$val",
                                  {
                                    "type": {
                                      "$cond": [
                                        // is this "type" already in array?
                                        {"$in": ["$$this.type", "$$val.type"]},
                                        // yes, so leave it as-is
                                        "$$val.type",
                                        // this is a new "type", so add it to array
                                        {"$concatArrays": ["$$val.type", ["$$this.type"]]}
                                      ]
                                    }
                                  }
                                ]
                              }
                            ]
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    ])
    

    Try it on mongoplayground.net.

    Login or Signup to reply.
  3. Here’s another, another way using a multiple "$map" and "$setUnion" to get unique array members.

    db.collection.aggregate([
      {
        "$set": {
          // rewrite fruits
          "fruits": {
            "$map": {
              // map over unique fruits
              "input": {"$setUnion": "$fruits.fruit"},
              "as": "theFruit",
              "in": {
                // set fruit
                "fruit": "$$theFruit",
                // "type" are unique elements of fruits.type
                // where fruits.fruit == theFruit
                "type": {
                  "$setUnion": {
                    "$map": {
                      "input": {
                        "$filter": {
                          "input": "$fruits",
                          "as": "obj",
                          "cond": {"$eq": ["$$obj.fruit", "$$theFruit"]}
                        }
                      },
                      "in": "$$this.type"
                    }
                  }
                }
              }
            }
          }
        }
      }
    ])
    

    Try it on mongoplayground.net.

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