Home > Software design >  Mongoose conditional update with array of objects
Mongoose conditional update with array of objects

Time:01-03

I have a Cart schema in Mongoose including CartItems as an array of objects.

{
  _id: string;
  items: [
    {
      product: string;
      options: {
        size: string;
        color: string;
      }
      quantity: number;
    }
  ]
}

In this case, is it possible to push an item when product and options doesn't match or add quantity when it does?

Here is the code that I have tried which does not work.

Cart.findByIdAndUpdate(_id, {
    items: {
      $cond: [
        {
          // Does not work
          $elemMatch: {
            product,
            "options.color": options.color,
            "options.size": options.size,
          },
        },
        {
          // Should add quantity where it is matched.
        },
        {
          $push: {
            product,
            productName,
            options,
            quantity,
          },
        },
      ],
    },
  });

CodePudding user response:

Query

  • pipeline update requires MongoDB >= 4.2
  • newProduct is the product you want to add (a js variable)
  • check if product already exists => add not-exist? field
  • if not-exists add it in the end
  • else map to find it and update the quantity
  • unset the 2 fields newProduct and not-exists

*it does 2 array reads, alternative could be to use $reduce but if you have many products $concatArrays is slow to be inside a reduce, so this is faster solutions (even if reduce would read the array 1 time only)

*you need a method to do update with pipeline, i don't know if mongoose is updated to support it, we are MongoDB 5 so i guess it will be(java is), in worst case you can use updateCommand and call it with runCommand(...)

Test code here

update({"_id" : "1"},
[{"$set":
  {"newProduct":
   {"product":"p1",
    "options":{"size":"s1", "color":"c1"},
    "quantity":1}}},
 {"$set":
  {"not-exists?":
   {"$eq":
    [{"$filter":
      {"input":"$items",
       "cond":
       {"$and":
        [{"$eq":["$$this.product", "$newProduct.product"]},
         {"$eq":["$$this.options.size", "$newProduct.options.size"]},
         {"$eq":["$$this.options.color", "$newProduct.options.color"]}]}}},
     []]}}},
 {"$set":
  {"items":
   {"$cond":
    ["$not-exists?", {"$concatArrays":["$items", ["$newProduct"]]},
     {"$map":
      {"input":"$items",
       "in":
       {"$cond":
        [{"$and":
          [{"$eq":["$$this.product", "$newProduct.product"]},
           {"$eq":["$$this.options.size", "$newProduct.options.size"]},
           {"$eq":["$$this.options.color", "$newProduct.options.color"]}]},
         {"$mergeObjects":
          ["$$this", {"quantity":{"$add":["$$this.quantity", 1]}}]},
         "$$this"]}}}]}}},
 {"$unset":["not-exists?", "newProduct"]}])

Query2

  • if you don't want to use update pipeline you can do it with more queries

Check if exists

db.collection.find({
  "_id" : "1",
  "items": {
    "$elemMatch": {
      "product": "p1",
      "options": {
        "size": "s1",
        "color": "c1"
      }
    }
  }
})

If not exists

db.collection.update({
  "_id": "1"
},
{
  "$push": {
    "items": "NEWITEM"   //put here the new object
  }
})

else If exists

db.collection.update({"_id" : "1"},
{
  "$inc": {
    "items.$[i].quantity": 1
  }
},
{
  arrayFilters: [
    {
      "i.product": "p1",
      "i.options.size": "s1",
      "i.options.color": "c1"
    }
  ]
})
  • Related