Hello everyone,
I have been struggling with this for a while now. I could not find a definite answer in the documentation, on why my initial approach should not work.
Maybe I am understanding and using the SDK wrong, or maybe there is a better way of achieving this.
I am using the NodeJS SDK and all examples are in JS.
I have an array of objects, something like:
{
array: [
{ name: "Obj1" },
{ name: "Obj2" },
{ name: "Obj3" }
],
}
I want to
- (a) update the property of a single array element and
- (b) after the update insert a new element into the array.
Ideally, I would like to do this on one update operation, to save round-trips and to avoid beeing in an inconsistent state.
What I found is, that updating works fine, as long as I do not use an aggregation:
const { insertedId } = await col.insertOne({
array: [{ name: "Obj1" }, { name: "Obj2" }, { name: "Obj3" }],
});
await col.updateOne(
{
_id: insertedId,
},
[
{
$set: { "array.1.name": "Update second array element only" },
},
],
);
The snippet above works fine and only updates the second array element. BUT if I want to do the same in an aggregation, it does not work anymore:
const { insertedId } = await col.insertOne({
array: [{ name: "Obj1" }, { name: "Obj2" }, { name: "Obj3" }],
});
await col.updateOne(
{
_id: insertedId,
},
[
{
$set: { "array.1.name": "Update second array element only" },
},
],
);
This statement will update every field in the array, create a new object with the name 1
and a property called name
:
{
"_id": "67939109ca5e4686dc8f2c91",
"array": [
{
"1": {
"name": "Update second array element only"
},
"name": "Obj1"
},
{
"1": {
"name": "Update second array element only"
},
"name": "Obj2"
},
{
"1": {
"name": "Update second array element only"
},
"name": "Obj3"
}
]
}
The only alternative that I have found, is to use a combination of $arrayElemAt
with $mergeObject
to select the element in question, construct a new object containing the desired changes and then use $concantArray
and $slice
to construct a new array:
const { insertedId } = await col.insertOne({
array: [{ name: "Obj1" }, { name: "Obj2" }, { name: "Obj3" }],
});
await col.updateOne(
{
_id: insertedId,
},
[
{
$set: {
array: {
$concatArrays: [
[
{
$mergeObjects: [
{ $arrayElemAt: ["$array", 0] },
{ name: "Update first entry only" },
],
},
],
{ $slice: ["$array", 1, { $size: "$array" }] },
],
},
},
},
],
);
This solution seems to be very complicated, hard to read, hard to extend, and gets very complicated, if you work with nested arrays, i.e. doing an update in an array in an object of arrays.
I find this very confusing, why does the $set
behave differently, depending on whether it is part of an aggregation or not.
- Is this desired behavior?
- Is there a better way to achieve the same result?
Any help would be appreciated.
Best Regards
Aiko
Note: this is a re-upload of my first post: How to Update Properties of a single Array Element.. Same content, but the formatting was screwed and I have found no way to edit the existing post.