I am executing an update on a complex document in DocumentDB. I want to only update changed fields, without setting the object (and/or any nested objects) explicitly, to avoid overwriting existing data.
I am using TypeScript, and have written a function that rewrites the update to use dot notation for every field (to avoid overwriting objects).
The issue is that the document's fields may be null, so updating foo.bar.baz
if foo
or foo.bar
are null.
Example documents in the collection:
{
"foo": {
"bar": {
"baz": "myValue1"
}
}
}
{
"foo": {
"bar": null
}
}
{ "foo": null }
Let's say for either one of these documents, I want to set foo.bar.qux
to myValue2
.
This is just an example - I want to have a way to handle updates to arbitrary fields at any level of nesting.
I have tried adding $ifNull
to the update query:
db.collection.updateOne(
{ _id: 1 },
{
$set: {
"foo": { $ifNull: ["$foo", {}] },
"foo.bar": { $ifNull: ["$foo.bar", {}] },
"foo.bar.qux": "myValue2",
}
}
)
That doesn't work - if foo
is not null, then setting foo
to $ifnull: ['$foo', {}]
and then foo.bar
in the same update fails, since it results in two updates to the foo.bar
field in the same operation:
Cannot update 'foo.bar.qux' and 'foo.bar.qux' at the same time
The proper way to solve this would be an update using an aggregation pipeline using $mergeObjects
, but DocumentDB does not support updates with an aggregation pipeline.
What are my options here? I want the solution to be as generic as possible and not require any knowledge about the schema or the data, so I could use it for all of my updates.
I would say the most canonical way would be using an aggregation to wrangle your data and iterate through the aggregation output to update back to the collection one by one. In this way you can leverage most of the supported aggregation API in AWS documentDB. Potentially if you are doing a lot of updates, you will want to wrap them in bulk operations too
db.collection.aggregate([
{
"$set": {
"foo.bar.qux": "value2"
}
}
]).forEach(function(result){
db.collection.updateOne(
{"_id": result._id},
{"$set": {"foo": result.foo}}
);
})