I am trying to update only given fields in an Object. In this case the object is a user whose schema looks like this:
class BasicUser(BaseModel):
timestamp: datetime = Field(default_factory=datetime.now)
name: str = Field(...)
surname: str = Field(...)
email: EmailStr = Field(...)
phone: constr(
strip_whitespace=True,
regex=r"^[\ ]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$",
)
age: PositiveInt = Field(...)
This user is only a part of the entire document. The full document looks like this:
I am using FastAPI and I want to receive only the fields that need updating from the frontend, my endpoint looks like this:
@router.put('/user/{id}', response_description='Updates a user', response_model=DocumentWithoutPassword)
async def updateUser (id: str, user: BasicUserUpdate = Body(...)):
new_user = {k: v for k, v in user.dict().items() if v is not None}
new_user = jsonable_encoder(new_user)
if len(new_user) >= 1:
update_result = await dbConnection.update_one({"_id": id}, {"$set": { "user" : new_user}})
the body of the request can include any number of fields (includes only the fields that need updating) for example the body could look like:
{
"email" : "[email protected]"
}
or
{
"email" : "[email protected]",
"phone" : " 123456789"
}
The problem with the above code is that when the request arrives, instead of updating the fields, it overwrites the entire user with only the email (if the email was sent) or the email and phone (if they were both sent).
So my question is how can I update specific values in user without overwriting everything
e.g. I send {"email" : "[email protected]"}
as the body, but instead of updating the email and leaving everything else as-is.
This is the result:
CodePudding user response:
Assuming that the generated new_user
object does indeed only contain fields to change that are nested inside of the user
field in the document (which sounds like it is the case), then probably the most straightforward option here is to use an aggregation pipeline to describe the update. This approach allows you to access a variety of pipeline operators notably the $mergeObjects
operator. Changing the following line:
update_result = await dbConnection.update_one({"_id": id}, {"$set": { "user" : new_user}})
To something like:
update_result = await dbConnection.update_one({"_id": id}, [ { $set: { user: { $mergeObjects: [ "$user", new_user ] } } } ])
Should yield the results that you want. See a demonstration of it at this playground link.
One general question does come to mind. If these documents represent users, then is there any particular value in nesting all of the fields underneath a parent user
field? It probably doesn't make a big difference one way or another in the end, but certainly the fact that this question was asked helps demonstrate some minor additional friction that can be encountered by nesting data (especially if it is unnecessary).