11

I have an array of string (ObjectIds) in mongoDB. I want to toggle an element in that array: If that element exists in the array, remove it, otherwise push that element.

Example:

arr = ["5b608c0769698c3990c64ef3","5b608c0769698c3990c64ef4"]
element = ["5b608c0769698c3990c64ef3"]

Final array:

arr = ["5b608c0769698c3990c64ef4"]

My use-case:

I am creating a blog website and in the Blog Schema I am storing the id of each user who has liked that blog, so that I can highlight the like button when blog is shown to the user.

2
  • You cannot... You have to use two queries one for $pull and one $push for this Commented Jul 31, 2018 at 17:03
  • i think this is similar question stackoverflow.com/questions/50169062/… Commented Jun 13, 2020 at 19:15

2 Answers 2

8
+50

If you are updating the document, you can use the pipeline within the update. But this feature is available with MongoDB version 4.2 or later.

db.collection.update(
  { },
  [
     { 
          $set: { 
              arr: { 
                  $cond: [ { $in: [ element, "$arr" ] }, 
                           { $setDifference: [ "$arr", [ element ] ] }, 
                           { $concatArrays: [ "$arr", [ element ] ] } 
                  ] 
              }
          }
     }
  ]
)

NOTE: Assuming the variable element is a string value.

If you are just querying you can use the following aggregation:

db.collection.aggregate([
     { 
          $addFields: { 
              arr: { 
                  $cond: [ { $in: [ element, "$arr" ] }, 
                           { $setDifference: [ "$arr", [ element ] ] }, 
                           { $concatArrays: [ "$arr", [ element ] ] } 
                  ] 
              }
          }
     }
] )

But, if you are using MongoDB version earlier than 4.2, then you can use the aggregate output from above to update the document:

db.collection.aggregate( [
  // aggregation pipeine from above ...
] ).forEach( doc => db.collection.updateOne( { _id: doc._id }, { $set: { arr: doc.arr } } ) )
Sign up to request clarification or add additional context in comments.

3 Comments

This looks very promising, I'll be sure to try this out soon. Thanks!
Great answer! One small caveat, this will fail if $arr does not exist. A more robust solution involves nesting two $conds, for example: mongoplayground.net/p/rFFrkytAM1D
Code snippets are not be assumed as a programming solution as it is, to be used in an application. In an application there will be validations, error handling and scenarios for solving a business rule. This is meant to be a way of solving a code, more of understanding a technique or using an API. @djanowski Thanks for the comment.
1

Its maybe not the best solution, but a easy one. You can add your element to the array with $addToSet and check with nModified if you modified the array. 0 means it already exist, and 1 mean you added new Element to it.

So you use mongoose as you said then you have a Model lets say Post. I dont know witch backend langauge you use, but i will show it with node.js here:

let result = await Post.updateOne({ _id: "someId" }, { 
   $addToSet: {
      arr: element
   }
})

if(result.nModified === 0){
   //0 means, no modifikation, that means its already liked
   await Post.updateOne({ _id: "someId" }, {
      $pull: {
         arr: element
      }
   })
}
response.send("Done");

1 Comment

That was my previous implementation and what I wanted to avoid. Thank you none the less.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.