Updating multiple items in array and creating those that don't exsist

I’m trying to make a player inventory system, where all items the player has are in an array.

  owner: String,
  items: [ {name: String, amount: Number }]

if a player does something which rewards more then one item i would like to do it all in one query, currently i have this.

Inventory.findOneAndUpdate({ owner: "test" }, {
        $inc: {
            'items.$[aa].amount': 10,
            'items.$[bb].amount': 2,
            'items.$[cc].amount': 100,
            'items.$[dd].amount': 50
    }, {
        arrayFilters: [
            { 'aa.name': 'gold' },
            { 'bb.name': 'gems' },
            { 'cc.name': 'food' },
            { 'dd.name': 'wood' }

this works great if the player already has at least one of each item, but if they for instance don’t have any wood yet, wood doesn’t get added. how would i go about creating any items that don’t already exist.

I’ve looked around and I’m almost entirely certain I’ll need to use aggregation but having only picked up Mongo a couple days ago can’t figure out which parts I need.

You have embedded documents in an array and you want to update the document in that array. If there is a document doesn’t yet exist in that array, you can use $addToSet in your update statement.

The issue i have when using $addToSet is that even if I have the item, if the amount is different it still adds a new entry into the array since the objects are not fully equal.

This question here is almost exactly what i need. the example on the post shows how to add or ignore multiple objects based on keys. and the linked video shows to increment a value or create an entry using a single object’s key. but i have yet to find out how to combine them into one.

Update, i played around and eventually got something to work.

let test = [{name: "Gold", amount: 10}, {name: "Gems", amount: 2}, {name: "Food", amount: 100}, {name: "Wood", amount: 50}]

Inventory.updateOne({ owner: id }, [{
    $addFields: {
        items: {
            $reduce: {
                input: test,
                initialValue: "$items",
                in: {
                    $cond: [
                        { $in: ["$$this.name", "$items.name"] },
                            $map: {
                                input: "$$value",
                                as: "el",
                                in: {
                                    $cond: {
                                        if: { $ne: ["$$el.name", "$$this.name"] },
                                        then: "$$el",
                                        else: { name: "$$el.name", amount: { $sum: ["$$el.amount", "$$this.amount"] } }
                        { $concatArrays: [["$$this"], "$$value"] }

It most certainly doesn’t scale super well, and I’m open to improvements, but it does work for what i had in mind.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.