Home > Net >  How to safely swap values of two objects in array in one request?
How to safely swap values of two objects in array in one request?

Time:10-23

I'm storing the order of items in a specific context and want to allow changing the order of the items.

Example document:

{
 _id: docID,
 items: [
     {
         _id: 1,
         orderNr: 0,
     },
     {
         _id: 2,
         orderNr: 1,
     },
     {
         _id: 3,
         orderNr: 2,
     },
 ]
}

Solutions I could think of:

The 'brute' way to do this would be to find the document, change the array with JavaScript and use $set to swap the entire array. This seems unsafe.

The other way I thought of was using arrayFilters and using data from the client. This seems safe, since the array filters require that each item still has its old orderNr value, however it requires knowing the current orderNr. Example:

collection.findOneAndUpdate(
        { _id: new ObjectID(item1Data.contextID), "items._id": new ObjectID(item1Data._id) },
        {
          $set: {
            "items.$[item1].orderNr": item2Data.orderNr,
            "items.$[item2].orderNr": item1Data.orderNr,
          },
        },
        {
          arrayFilters: [
            { "item1._id": item1Data._id, "item1.orderNr": item1Data.orderNr },
            { "item2._id": item2Data._id, "item2.orderNr": item2Data.orderNr },
          ],
        }
      );

Is there a way I could just use the ID of both items to swap their orderNr values, without having to know the current orderNr from the client or from an extra request — using existing values straight from the document, during the request and just using ID to swap?

CodePudding user response:

Yes it's possible, you want to be using the aggregation pipeline updates framework, this will allow you to use aggregation expressions within your update body.

At this point you can achieve this update in multiple different ways, here is one way which I think is the most "readable". Essentially we iterate over all items using $map, and if the documents match based on the id we convert them:

const item1Data = { _id: 1 };
const item2Data = { _id: 3};

db.collection.update({},
[
  {
    $set: {
      item1: {
        $arrayElemAt: [
          {
            $filter: {
              input: "$items",
              cond: {
                $eq: [
                  "$$this._id",
                  item1Data._id
                ]
              }
            }
          },
          0
        ]
      },
      item2: {
        $arrayElemAt: [
          {
            $filter: {
              input: "$items",
              cond: {
                $eq: [
                  "$$this._id",
                  item2Data._id
                ]
              }
            }
          },
          0
        ]
      }
    }
  },
  {
    "$set": {
      items: {
        $map: {
          input: "$items",
          in: {
            $switch: {
              branches: [
                {
                  case: {
                    $eq: [
                      "$$this._id",
                      item1Data._id
                    ]
                  },
                  then: {
                    $mergeObjects: [
                      "$$this",
                      {
                        orderNr: "$item2.orderNr"
                      }
                    ]
                  }
                },
                {
                  case: {
                    $eq: [
                      "$$this._id",
                      item2Data._id
                    ]
                  },
                  then: {
                    $mergeObjects: [
                      "$$this",
                      {
                        orderNr: "$item1.orderNr"
                      }
                    ]
                  }
                }
              ],
              default: "$$this"
            }
          }
        }
      }
    }
  },
  {
    $unset: [
      "item1",
      "item2"
    ]
  }
])

Mongo Playground

  • Related