154

Is there a way to update values in an object?

{
  _id: 1,
  name: 'John Smith',
  items: [{
     id: 1,
     name: 'item 1',
     value: 'one'
  },{
     id: 2,
     name: 'item 2',
     value: 'two'
  }]
}

Lets say I want to update the name and value items for item where id = 2;

I have tried the following w/ mongoose:

var update = {name: 'updated item2', value: 'two updated'};
Person.update({'items.id': 2}, {'$set':  {'items.$': update}}, function(err) { ...

Problem with this approach is that it updates/sets the entire object, therefore in this case I lose the id field.

Is there a better way in mongoose to set certain values in an array but leave other values alone?

I have also queried for just the Person:

Person.find({...}, function(err, person) {
  person.items ..... // I might be able to search through all the items here and find item with id 2 then update the values I want and call person.save().
});
aaaidan
  • 7,093
  • 8
  • 66
  • 102
lostintranslation
  • 23,756
  • 50
  • 159
  • 262

15 Answers15

244

You're close; you should use dot notation in your use of the $ update operator to do that:

Person.update({'items.id': 2}, {'$set': {
    'items.$.name': 'updated item2',
    'items.$.value': 'two updated'
}}, function(err) { ...
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • Hello, when I do exactly you suggested. board.update({ _id: board._id,"users.username": req.user.username}, {$set: {"users.$.lastViewed": new Date()}}, function (err) {}); I got an error: cannot use the part (users of users.username) to traverse the element ({users: [ { username: "nlm", _id: ObjectId('583c8cc3813daa6c29f69cb0'), status: "invited", role: "reviewer" } ]}). Is there anything I did differently? – Peter Huang Nov 28 '16 at 20:04
  • 7
    This works great, thank you. However, is there any way to create an item if it's not in the array? – Sergey Mell Oct 12 '17 at 11:24
  • @SergeyMell if I think that might be a solution: https://stackoverflow.com/a/33577318/6470918 – Grzegorz Brzęczyszczykiewicz Oct 18 '18 at 20:26
  • How do I update the name for id = 2 in all documents in a collection? – Rod Nov 17 '18 at 04:04
  • 2
    How would this work if you had an array of objects inside an array? Like instead of just `items.$.name`, you had `items.users.$.status`? would that work? – Stevie Star May 07 '19 at 16:12
  • hey, i tried this doing but after doing this i have to make two patch request from postman, then only it is updating the value. if any one can help please reply! – meenachinmay Mar 01 '20 at 20:32
  • This does not work for me. It always update the first element of the array. However, I solved the problem using arrayFilters. https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/ – muhammad800804 Apr 11 '20 at 17:08
  • And 10 years after this question was originally asked, this solution still stands :-) – ChrisRich Jun 08 '23 at 02:36
59
model.update(
    { _id: 1, "items.id": "2" },
    {
        $set: {
            "items.$.name": "yourValue",
            "items.$.value": "yourvalue",
         }
    }
)

MongoDB Document

Kaspar Lee
  • 5,446
  • 4
  • 31
  • 54
Howard
  • 701
  • 5
  • 5
27

There is a mongoose way for doing it.

const itemId = 2;
const query = {
  item._id: itemId 
};
Person.findOne(query).then(doc => {
  item = doc.items.id(itemId );
  item["name"] = "new name";
  item["value"] = "new value";
  doc.save();

  //sent respnse to client
}).catch(err => {
  console.log('Oh! Dark')
});
Anees Hameed
  • 5,916
  • 1
  • 39
  • 43
14

There is one thing to remember, when you are searching the object in array on the basis of more than one condition then use $elemMatch

Person.update(
   {
     _id: 5,
     grades: { $elemMatch: { grade: { $lte: 90 }, mean: { $gt: 80 } } }
   },
   { $set: { "grades.$.std" : 6 } }
)

here is the docs

Gopal
  • 455
  • 5
  • 14
7

For each document, the update operator $set can set multiple values, so rather than replacing the entire object in the items array, you can set the name and value fields of the object individually.

{'$set':  {'items.$.name': update.name , 'items.$.value': update.value}}
Shaun
  • 284
  • 2
  • 3
7

Below is an example of how to update the value in the array of objects more dynamically.

Person.findOneAndUpdate({_id: id}, 
{ 
  "$set": {[`items.$[outer].${propertyName}`]: value} 
},
{ 
  "arrayFilters": [{ "outer.id": itemId }]
},
function(err, response) {
  ...
})

Note that by doing it that way, you would be able to update even deeper levels of the nested array by adding additional arrayFilters and positional operator like so:

"$set": {[`items.$[outer].innerItems.$[inner].${propertyName}`]: value} 

"arrayFilters":[{ "outer.id": itemId },{ "inner.id": innerItemId }]

More usage can be found in the official docs.

Jakub A Suplicki
  • 4,586
  • 1
  • 23
  • 31
  • Cool, maybe please add a link to the official docs about this thing – Tayyab Ferozi Dec 17 '20 at 17:25
  • bro, `"arrayFilters":[{$and:[{ "outer.id": itemId },{ "outer.name": itemName }]}]` . It's not working with mongoose 5.2 and findOneAndUpdate. getting "No array filter found for identifier item ...' . please provide some insights – Firoj Siddiki Aug 03 '21 at 11:01
  • @FirojSiddiki I believe your issue is with an outdated MongoDB Node.JS driver. Please update it to 3.0+. Also, I do not think you need `$and` in there. – Jakub A Suplicki Aug 03 '21 at 22:05
6

cleaner solution using findOneAndUpdate

  await Person.findOneAndUpdate(
    { _id: id, 'items.id': 2 },
    {
      $set: {
        'items.$.name': 'updated item2', 
        'items.$.value': 'two updated',
      }
    },
   );
samehanwar
  • 3,280
  • 2
  • 23
  • 26
0

In Mongoose, we can update array value using $set inside dot(.) notation to specific value in following way

db.collection.update({"_id": args._id, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
  • Would You please edit answer with explanations, or relate to the question? – NIKHIL C M Dec 28 '17 at 07:29
  • 1
    Could you possibly take a look at my question, mongoose is removing the part of my query which uses $[] even though it works in the CLI https://stackoverflow.com/questions/64223672/why-is-mongoose-removing-part-of-my-query – nrmad Oct 06 '20 at 11:06
0

Having tried other solutions which worked fine, but the pitfall of their answers is that only fields already existing would update adding upsert to it would do nothing, so I came up with this.

 Person.update({'items.id': 2}, {$set: {
    'items': { "item1",  "item2",  "item3",  "item4" } }, {upsert: 
true })
Chukwuemeka Maduekwe
  • 6,687
  • 5
  • 44
  • 67
0

I had similar issues. Here is the cleanest way to do it.

    const personQuery = {
       _id: 1  
    }

    const itemID = 2;

    Person.findOne(personQuery).then(item => {
       const audioIndex = item.items.map(item => item.id).indexOf(itemID);
       item.items[audioIndex].name = 'Name value';
       item.save();
    });
Magellan
  • 1,224
  • 9
  • 16
0

Found this solution using dot-object and it helped me.

import dot from "dot-object";

const user = await User.findByIdAndUpdate(id, { ...dot.dot(req.body) });
Cuado
  • 501
  • 1
  • 5
  • 13
0

I needed to update an array element with dynamic key-value pairs. By mapping the update object to new keys containing the $ update operator, I am no longer bound to know the updated keys of the array element and instead assemble a new update object on the fly.

update = {
  name: "Andy",
  newKey: "new value"
}
new_update = Object.fromEntries(
  Object.entries(update).map(
    ([k, v], i) => ["my_array.$." + k, v]
  )
)
console.log({
  "$set": new_update
})
0
person.updateMany(
  { "items.id": id },
  {
    $set: {
      "items.$.name": name,
      "items.$.value": value,
    },
  },
  { new: true }
)
   .then((update) => 
  {res.send(update)}).
 catch((err)=>{
res.send(err)})
bho0t
  • 26
  • 3
  • 1
    Your answer could be improved by adding more information on what the code does and how it helps the OP. – Tyler2P Mar 05 '23 at 18:15
-1

In mongoose we can update, like simple array

user.updateInfoByIndex(0,"test")

User.methods.updateInfoByIndex = function(index, info) ={
    this.arrayField[index]=info
    this.save()
}
Vilintritenmert
  • 125
  • 1
  • 3
  • 9
-1
update(
    {_id: 1, 'items.id': 2},
    {'$set': {'items.$[]': update}},
    {new: true})

Here is the doc about $[]: https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up.S[]

M--
  • 25,431
  • 8
  • 61
  • 93