Updating specific array elements in a pipeline style update

Dear Community,

let’s assume, we have a document something like this in our collection:

 {
  "top_level": [
    {
      "name": "1",
      "second_level": [
        {
          "surname": "2"
        }
      ]
    },
    {
      "name": "3",
      "second_level": [
        {
          "surname": "4"
        },
        {
          "surname": "5"
        }
      ]
    }
  ]
}

If we want to update only the surname property of only one entry, the usual update ends up to look like this:

db.collection.updateMany(
   {},
   {'$set': {'top_level.1.second_level.1.surname': 'Hello World'}}
)

Thus leaving us with:

{
  "top_level": [
    {
      "name": "1",
      "second_level": [
        {
          "surname": "2"
        }
      ]
    },
    {
      "name": "3",
      "second_level": [
        {
          "surname": "4"
        },
        {
          "surname": "Hello World"
        }
      ]
    }
  ]
}

Now the issue is I am failing to produce the same behaviour using pipeline style update statements. Can someone advice on how to achieve this behaviour with a pipeline style update. Many thanks in Advance.

I forgot to add:

I would like to use pipelines, because there is no way of removing an item from an array in an atomic way based on its index without using pipelines (at least upto my [limited] knowledge).

Me and my colleagues try to implement patching (RFC 6902). This allows removing and altering at the same time (given it works on different arrays), so we’d like to get it to work.

What determines that you want element 1 of top_level and element 1 of second_level?

If the index is determined by other field of the array, then you are better off using arrayFilters rather than indexes.

For example if you want to update top_level.1.second_level.1 because top_level.1.name is 3 and the corresponding second_level.1.surname is 5 then specifying arrayFilters should work.

With indexes rather than arrayFilters, something like the following might work:

top_level_index = 1
top_level_element = { "$arrayElemAt" : [ "$top_level" , top_level_index ] }
second_level = { "$getField" : { "field" , "second_level" , top_level_element } }
second_level_index = 1
second_level_element = { "$arrayElemtAt" : [ second_level , second_level_index ] }
new_value = "Hello World" 
$concatArrays : [
    { "$slice" : [ "$top_level" , 0 , top_level_index ] } ,
    [ { "$mergeObjects" : [
        top_level_element ,
        { "second_level" : { "$concatArrays" : [
            { "$slice" : [ second_level  , 0 , second_level_index ] } ,
            [ { "$mergeObjects" : [
                second_level_element ,
                { "surname" : new_value }
            ] } ] ,
            { "$slice" : [ second_level , 0 , second_level_index + 1 ] } ,
        ] } }
    ] } ] ,
    { "$slice" : [ "$top_level" , top_level_index + 1 ] }
]

Ugly and rather complex but not that complicated.

Thanks for your response.

Basically, we are using a monogDB behind a run-of-the-mill CRUD API. So which index to manipulate is given by the outside patch request. There are workaround solutions, yes. All these break the atomicity bar yours potentially. I had hoped that there was an easy implementation in mongo query language, as the standard JSON patching is so similar to the general document structure in a MongoDB.

Thank you for your answer. I will try it out. Anyhow, it becomes rather involved the more nested lists you amass. Will have to think about this.

I have just reread the whole thread and I am not sure about what you mean with

What I thought.

May be you could take a look at Compass source code. They surely implement a similar use-case for when you update an array.