Upsert nested object

{
    "_id" : ObjectId("63a00087347cec20308de2ec"),
    "registration" : {
        "email" : "admin@admin.com",
        "password" : "password"
    },
    "profile" : {
        "name" : "My Name",
        "mark" : "r4u4tlbOSq",
        "category" : "general",
       //other 60 fields
    }
}

I want to update the sub-document “profile” with

//req.body
{      // name field not present
        "mark" : "newmark",
        "category" : "general",
       //other 60 fields
 }

The code I wrote below and it is deleting profile.name, but I want to preserve profile.name. I want to preserve all the fields that are either not present in req.body or is empty string. How can I do that.

const query = { _id: ObjectId(req.userid) };

  const update = { $set: {"profile" :{...req.body}}};

  const options = { upsert: true };

  await colname.updateOne(query, update, options);

Actual Result

{
    "_id" : ObjectId("63a00087347cec20308de2ec"),
    "registration" : {
        "email" : "admin@admin.com",
        "password" : "password"
    },
    "profile" : {
        "mark" : "newmark",
        "category" : "general",
       //other 60 fields
    }
}

Expected Result

{
    "_id" : ObjectId("63a00087347cec20308de2ec"),
    "registration" : {
        "email" : "admin@admin.com",
        "password" : "password"
    },
    "profile" : {
        "name" : "My Name",
        "mark" : "newmark",
        "category" : "general",
       //other 60 fields
    }
}

Thank You

1 Like

The problem with standard $set updates is that the update can’t access the internal fields for the document being processed.

You can do it using a pipeline-update instead, if I get your idea correctly. See mongoplayground, and sample below:

db.collection.update({}, //your search query
[
  {
    $addFields: {
      "profile": { //overwrite profile field
        "$mergeObjects": [
          "$$ROOT.profile",// $$ROOT means current object
          {
            "hello": "world" // your update object here
          }
        ]
      }
    }
  }
],
{
  upsert: true
})

There may be better ways.

1 Like

I cannot decide if it is a better way or not, just that it is different.

When you use

you ask to set the field profile to the given value. To set a field inside an object you use the dot notation like:

{ "$set" : {
    "profile.mark" : "new_mark" ,
    "profile.category" : "new_category"
} }

In this case the difficulty is to go from req.body to the appropriate dot notation. In JS, it should be easy to do that with something like

Object.keys(req.body).map(...)

But that would be dangerous since you need to trust the req.body you received. But you already do that with

2 Likes

You are correct @steevej, nice explanation. I have forgotten some syntax, maybe this

const myQueryObj = { }
const entries = Object.entries(req.body)
for (const [key,val] of entries){
  myQueryObj[`options.${key}`] =  val
}
2 Likes

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.