Unexpected behaviour with $addFields and projection on existing field name

Hello : )

I inserted those data,1 document and i tried to replace the existing fields,with a single value,
an array,and an onbject and i got un-expected for me results,i just wanted to replace the old value.


{
  "mysingle1": 1,
  "mysingle2": 1,
  "mysingle3": 1,
  
  "myarray1": [1,2],
  "myarray2": [1,2],
  "myarray3": [1,2],
  
  "myobject1": {"afield": ""},
  "myobject2": {"afield": ""},  
  "myobject3": {"afield": ""}  
}

And i used $addFields.To always add a new field,that already existed.
Reading the documentatation from the IMPORTANT + example after

seemed that new field value,will replace the existing but this is not the case.
Sometimes it replaces,sometimes it updates,and the update result looks unexpected.
Maybe in the documentation somewhere it resolves it but this is why i thought it is always replace.

$addFields behaviour

single -> add single/array/document => replace

array  -> add  single/array    => replace
array  -> add document         => update,all members of the array becomes that document

  {"$addFields" : {"myarray3" : {"afield1" :""}}}
  became 
  {"myarray3" : [{"afield1" :""} {"afield1" :""}]}

document -> add single/array   => replace
document -> add document       => update document,added becomes its member
  
  {"$addFields" : {"myobject3" : {"afield1" :""}}}
  became
  {"myobject3" {"afield" "", "afield1" ""}}

Then i used the same but with $project,results was like addFields but

document -> add document => replace

This is complicated for me ,i just want to replace the value.
Without thinking what was there,and what i add.
Why it works this way?Where this helps?
Especially the update of the array with the document as all its members looks so un-expected.
There is a way to just replace the old field,and never update,using one stage?

Thank you.

Hello @Takis,

Yes, your observations are correct. Here are some clarifications (I think).

document → add document => update document, added becomes its member

db.test.aggregate([
  { "$addFields" : { "myobject3" : { "afield1" : "" }}}
])
returns the updated field as:  { "myobject3": { "afield": "", "afield1": "" }}

This behavior is correct. It is adding a new field afield1 to the myobject3 sub-document.

If you want to replace the "myobject3": {"afield": "" } with "myobject3": {"afield1": "" } then you can use one of the following:

{"$addFields" : { "myobject3": { "afield1" : "" }, "myobject3.afield" : "$$REMOVE" } }

-or-

{"$addFields" : { "myobject3.afield1" : "", "myobject3.afield" : "$$REMOVE" } }

-or-

{"$project" : { "myobject3.afield1" : "" } }

NOTE: The $project will exclude all other fields, except the _id (which is included by default).



document → add document => replace
Then i used the same but with $project,results was like addFields but

This behaviour is correct:

db.test.aggregate([
  { "$project" : { "myobject3" : { "afield1" : "" } } }
])
returns => "myobject3" : { "afield1" : "" }

This is because $project ignores all the fields that are not included, except the _id (which is included by default). In this case only the "myobject3.afield1" is included…



array → add document => update, all members of the array becomes that document
{“$addFields” : {“myarray3” : { “afield1” : “” } } }
became
{“myarray3” : [{“afield1” :“”} {“afield1” :“”}]}

Yes, the aggregation:

db.test.aggregate([
  { "$addFields" : { "myarray3" : { "afield1" : "" } } },
])
returns the array updated as: {"myarray3" : [{"afield1" :""} {"afield1" :""}]}

The behavior is same with $project also. Adds, the sub-document as elements of the array, replacing the existing array elements.

I am afraid this is the expected behavior, according to this MongoDB JIRA: $project computed fields on arrays sets every element of the array – inconsistent with $set

Hello ,and thank you for the reply.

To me the intuitive was to replace the value always.
But this is not the case,and from the JIRA looks like they decided to leave it like this.
If they update the documentation also it would be nice.

I used a custom notation ,and a function that auto produce 3 stages,so i can always replace
if i want,hopefully without big perfomance cost.

We will see : )