164

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().
});

15 Answers 15

253

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) { ...
Sign up to request clarification or add additional context in comments.

8 Comments

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?
This works great, thank you. However, is there any way to create an item if it's not in the array?
@SergeyMell if I think that might be a solution: stackoverflow.com/a/33577318/6470918
How do I update the name for id = 2 in all documents in a collection?
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?
|
62
model.update(
    { _id: 1, "items.id": "2" },
    {
        $set: {
            "items.$.name": "yourValue",
            "items.$.value": "yourvalue",
         }
    }
)

MongoDB Document

1 Comment

Instead of update it's better to use updateOne or updateMany
29

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')
});

4 Comments

This method is handy if you are doing an operation on the parent document as well as the array item.
When I update the document, also updates _id of that document? It's strange.
had to change item = doc.items.id(itemId ) line to item=doc.items.find((item)=>item.id===itemId) and then it worked for me.
You can use dot notation to make this even easier to read. ie: change item["name"]= "new name" to item.name = "new name"
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

Comments

9

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.

3 Comments

Cool, maybe please add a link to the official docs about this thing
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
@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.
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}}

Comments

7

cleaner solution using findOneAndUpdate

  await Person.findOneAndUpdate(
    { _id: id, 'items.id': 2 },
    {
      $set: {
        'items.$.name': 'updated item2', 
        'items.$.value': 'two updated',
      }
    },
   );

Comments

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}})

2 Comments

Would You please edit answer with explanations, or relate to the question?
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 stackoverflow.com/questions/64223672/…
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 })

Comments

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();
    });

1 Comment

I think In-memory manipulation could lead to processing of outdated data duing very high-write periods.
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) });

Comments

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
})

Comments

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)})

1 Comment

Your answer could be improved by adding more information on what the code does and how it helps the OP.
-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()
}

1 Comment

Your answer would be more helpful with some explanation. Please consider editing it.
-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[]

2 Comments

Unfortunately, that doesn't work. it won't replace the whole array
It is supposed to replace the elements(item having id as 2) of the items array

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.