I’ve a customer document with attributes array. I want to insert new attributes and update already existing ones.
Already existing "attributes" within the document:
[{
key: "email",
value: "[email protected]"
}, {
key: "name",
value: "Somebody"
}]
I want to** add or update** within "attributes"
[{
key: "email",
value: "[email protected]"
}, {
key: "mobile",
value: "1234567890"
}]
So, the result should be:
[{
key: "email",
value: "[email protected]"
},{
key: "name",
value: "Somebody"
},{
key: "mobile",
value: "1234567890"
}]
I achieved the result using below written code. But I want to use only MongoDB to get the same result.
const { attributes, uid } = req.body;
const customer: any = await getCustomer({ uid });
const oldAttributes = customer?.attributes.filter((attr: any) => !attributes.find((att: any) => att.key === attr.key));
const updatedAttributes = [...oldAttributes, ...attributes];
const response = await updateCustomer({ uid }, { $set: { attributes: updatedAttributes } } );
2
Answers
You can set
upsert: true
inoptions
when you callupdateCustomer
as follows:You can refer to more at: https://www.mongodb.com/docs/drivers/node/current/fundamentals/crud/write-operations/upsert/
Since using
upsert
would overwrite the old attributes with the new attributes, rather than updating just the attributes array, this would need a full pipeline, ending with amerge
back into the collection.Assuming a document like this:
Steps at a high-level:
{key: "<key_name>", value: "<value>"}
to an object having{<key_name>: <value>, ...}
attributes
.{key: "<key_name>", value: "<value>"}
User input would be required for the first
$match
step for the document_id
and then for the$mergeObjects
step where you add user provided values.Notes:
$let
or nested"$set": { "attributes": ... }
but it feels cleaner and clearer as separate pipeline steps.[{"key": "name", "value": "$attribute.name}, {"key": "email", "value": "$attribute.value"}, ...]
in theobjecToArray
step not require the re-mapping of labels after that.merge
, I have putwhenNotMatched: "fail"
since you probably can’t use this for records which don’t already exist.db.collection.update()
instead of aggregate. So the final$merge
is not required. But you will need to add{upsert: false, multi: false}
for the 3rd param; which finally becomes the same length/complexity as the original anyway.Result:
Mongo Playground
Alternate, with
collection.update()
.PS. I think
$objectToArray
really needs some options for customising the label for the key and value.