Given this simple example, an item saved as:
{
"test": {
"a1": "a1"
}
}
When i call collection.updateOne with $set and the following object:
await collection.updateOne(doc, {$set: {"test": {"a2": "a2"}}}, {upsert: true});
it replaces the saved test object to:
{
"test": {
"a2": "a2"
}
}
The a1 key will no longer exist as it also does not exist in the object sent.
I’m trying to update saved items with the values passed in the object **without ** removing non-existing values, I mean:
{"test": {"a2": "a2"}} would make the item be updated to:
{
"test": {
"a1": "a1",
"a2": "a2"
}
}
I have written a function to recursively parse objects/arrays and build a query for this use case.
In my mind it looks like a mess and I’m not sure if it’s correct:
// The function attempts to parse arrays and objects recursively and recreate the query
function buildUpdateQuery(object, prefix = '')
{
let updateQuery = {};
for (let [key, value] of Object.entries(object))
{
let newPrefix = prefix ? `${ prefix }.${ key }` : key;
if (Array.isArray(value))
{
for (let i = 0; i < value.length; i++)
{
let arrayPrefix = `${newPrefix}.${i}`;
if (typeof value[i] === 'object' && value[i] !== null)
updateQuery = {...updateQuery, ...buildUpdateQuery(value[i], arrayPrefix)};
else
updateQuery[arrayPrefix] = value[i];
}
}
else if (typeof value === 'object' && value !== null)
updateQuery = {...updateQuery, ...buildUpdateQuery(value, newPrefix)};
else
updateQuery[newPrefix] = value;
}
return updateQuery;
}
export const set = async (req, res /*key, object*/) => // Using mongodb and express
{
try
{
const [collection, key] = req.body.key.split("|");
if (collection === undefined || key === undefined)
return res ? res.send('fail') : 'fail'
const doc = buildUpdateQuery(req.body.object);
let r = await db.collection(collection).updateOne({ [key]: { $exists: true } }, { $set: doc }, { upsert: true });
r = r.modifiedCount ? 'ok' : 'fail';
return res ? res.send(r) : r
}
catch (error)
{
console.log(`\n[set]\n${ JSON.stringify(req.body, null, 2) }\n\n`, error);
return res ? res.send('fail') : 'fail'
}
}
Calling the set function with:
{"key": "vars|test", "object": {"test": {"a2": "a2"}}}
at const doc = buildUpdateQuery(req.body.object);
doc is {test.a2: 'a2'} and it indeed updates
{"test": {"a1": "a1"}} to {"test": {"a1": "a1"},{"a2":"a2"}}
I’m afraid it could mess up a collection at some time, maybe parsing something wrong.
I’m aware that i could first get the existing object with:
const document = await db.collection(collection).findOne({ [key]: { $exists: true } });
and then modify it with the new values, but that’s not what I’m looking for.
Is there any built-in method in MongoDB for such a thing?
If not, is the buildUpdateQuery correct or there’s any other better approach for this use case?