unaligned
unaligned

Reputation: 160

Updating nested objects in MongoDB

I am trying to update a nested document in MongoDB that looks similar to below (shortened to be concise).

{
  "cols": "20",
  "saveTime": "2014-06-15-10:44:09",
  "rows": "20",
  "gameID" : "66f2497c-7a2b-4210-a06b-80be0e6a8fd8",
  "players": [
    {
      "id": "Inhuman",
      "num": "1",
      "color": "00ff00ff",
      "name": "badComputer",
      "type": "1"
    },
    <...>
  ],
  "nodes": [
    {
      "g": "1",
      "c": "0",
      "r": "0",
      "a": "0",
      "o": ""
    },
    {
      "g": "1",
      "c": "0",
      "r": "1",
      "a": "0",
      "o": ""
    }
    <...>
  ],
}

What I am trying to do is update one of the nodes. For example, I want to change the node:

{ "g": "1", "c": "0", "r": "0", "a": "0", "o": ""}

to

{ "g": "1", "c": "0", "r": "0", "a": "5", "o": ""}

I have tried using the dot (.) notation, with the $set command, ala:

db.game.update({"gameID" : "66f2497c-7a2b-4210-a06b-80be0e6a8fd8"}, { $set: {"nodes.r":"0",   "nodes.c":"0", "nodes.a":"5"}}),

But that does not give me the expected behavior because I'm updating all nodes with the same r and c values. This is obviously not what I want, but I do not see how to update a specific piece of this document. Does anyone have any idea how to do this?

Upvotes: 1

Views: 7178

Answers (2)

Neil Lunn
Neil Lunn

Reputation: 151072

If you are looking to update a specific item in your "nodes" array that you do not know the position of but you know the "criteria" to match that item, then you need the $elemMatch operator along with the positional $ operator in the update side:

db.game.update(
    {
        "gameID" : "66f2497c-7a2b-4210-a06b-80be0e6a8fd8",
        "nodes": { "$elemMatch": { "g": 1, "r": 0 } } 
    },
    { "$set": { "nodes.$.c":"0", "nodes.$.a":"5" } }
)

The positional $ operator contains the matched "index" position of the first element "matched" by your query conditions. If you do not use $elemMatch and use the "dot notation" form instead, then the match is only valid for the whole document containing values that would be true and does not reflect the "position" of the element that matches both of the field conditions.

Care must be taken that this is the "first" match, and typically expected as the only match. The reason being that the positional operator will only contain the "first" position where there were multiple matches. To update more than one array item matching the criteria in this way, then you need to issue the update several times until the document is no longer modified.

For a "known" position you can always directly use the "dot notation" form, including the position of the element that you wish to update:

db.game.update(
    {
        "gameID" : "66f2497c-7a2b-4210-a06b-80be0e6a8fd8",
    },
    { "$set": { "nodes.0.c":"0", "nodes.0.a":"5" } }
)

So the index position is 0 for the first element, 1 for the second element and so on.

Noting that in both cases, you only need to pass to $set the fields you actually want to change. So unless you are unsure of a value being present ( which would not be the case if that was your query ) then you do not need to "set" fields to the value they already contain.

Upvotes: 3

orepor
orepor

Reputation: 945

To update specific node - you would need to put that in the query part of your search.

As in

  db.game.update({"gameID" : "66f2497c-7a2b-4210-a06b-80be0e6a8fd8","nodes.r":"0", 

  "nodes.c":"0", "nodes.a":"5" }, { $set: {"nodes.$.r":"0",   "nodes.$.c":"0", "nodes.$.a":"5"}})

You see the $ sign takes the node object it found that matches the first part (query) of the call, and sends you there in the second part (projection) part of your call. Also check out this question

Upvotes: 0

Related Questions