Consider this straightforward example, where an item is saved as:
{
"test": {
"a1": "a1"
}
}
When collection.updateOne
is called with $set
and the following object:
await collection.updateOne(doc, {$set: {"test": {"a2": "a2"}}}, {upsert: true});
it updates the saved test
object to:
{
"test": {
"a2": "a2"
}
}
The key a1
will no longer exist since it is not present in the sent object.
The objective is to update saved items with the values given in the object **without** removing non-existing values. For instance,
{"test": {"a2": "a2"}}
would result in updating the item to:
{
"test": {
"a1": "a1",
"a2": "a2"
}
}
A function has been devised to recursively parse objects/arrays and construct a query for this purpose.
However, there are doubts regarding its correctness and clarity:
// 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'
}
}
Invoke the set
function like so:
{"key": "vars|test", "object": {"test": {"a2": "a2"}}}
at
const doc = buildUpdateQuery(req.body.object);
doc
resolves to {test.a2: 'a2'}
updating
{"test": {"a1": "a1"}}
to {"test": {"a1": "a1"},{"a2":"a2"}}
There is concern that this could potentially disrupt a collection by inaccurately parsing data. Alternative methods have been considered, such as retrieving the existing object first:
const document = await db.collection(collection).findOne({ [key]: { $exists: true } });
and then applying the modifications, although this approach may not be ideal.
Is there a native solution in MongoDB for this scenario?
If not, is the implementation of buildUpdateQuery
accurate or is there a preferable alternative?