1

I'm updating the age and name of a character with a specific _id from an array of characters that is inside a document of model Drama.

The document I'm working with:-

{
    "_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
    "characters" : [ 
        {
            "_id" : ObjectId("619fdac5a03c8b10d0b8b13c"),
            "age" : "23",
            "name" : "Vinay", 
        }, 
        {
            "_id" : ObjectId("619fe1d53810a130207a409d"),
            "age" : "25",
            "name" : "Raghu", 
        }, 
        {
            "_id" : ObjectId("619fe1d53810a130207a502v"),
            "age" : "27",
            "name" : "Teju", 
        }
    ],
}

So to update the character Raghu I did this:-

const characterObj = {
  age: "26",
  name: "Dr. Raghu",
};

Drama.updateOne(
  { _id: req.drama._id, "characters._id": characterId },
  {
    $set: {
      "characters.$": characterObj,
    },
  },
  function(err, foundlist) {
    if (err) {
      console.log(err);
    } else {
      console.log("Update completed");
    }
  }
);

// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")

This updated the character but it also assigned a new ObjectId to the _id field of the character. So, I'm looking for ways on how to prevent the _id update.

Also, I know I can set the individual fields of character instead of assigning a whole new object to prevent that but it will be very tedious if my character's object has a lot of fields.

//Not looking to do it this way
$set: {
        "characters.$.age": characterObj.age,
        "characters.$.name": characterObj.name,
      },

Thanks.

Deepanshu
  • 890
  • 5
  • 18

3 Answers3

1

I found something here, just pre define a schema (a blueprint in a way) that affects the id

var subSchema = mongoose.Schema({
    //your subschema content
},{ _id : false });

Stop Mongoose from creating _id property for sub-document array items

Or I would say, when you create a character assign it a custom id from the start, that way it will retain that id throughout.

ConnerWithAnE
  • 1,106
  • 10
  • 26
  • Thanks very much, You provided a good alternative but tbh I do want to retain the auto generated _id of the array documents instead of creating my own custom id and managing it. So, do you know about any direct way to prevent _id change on update in mongoose or mongoDB? – Deepanshu Nov 26 '21 at 13:41
0

I'm leaving this question open as I would still like to see a simpler approach. But for now, I did find one easy alternative solution for this issue which I'm will be using for some time now until I find a more direct approach.

In short - Deep merge the new object in the old object using lodash and then use the new merged object to set field value.

For example, let's update the character Raghu from my question document:-

  1. First install lodash(Required for deep merging objects) using npm:
$ npm i -g npm
$ npm i --save lodash
  1. Import lodash:
const _ = require("lodash");
  1. Now update the character Raghu like this:-
const newCharacterObj = {
  age: "26",
  name: "Dr. Raghu",
};

Drama.findById(
  { _id: req.drama._id, "characters._id": characterId },
  "characters.$",
  function(err, dramaDocWithSpecificCharacter) {
    console.log(dramaDocWithSpecificCharacter);
    // ↓↓↓ console would log ↓↓↓
    // {
    //     "_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
    //     "characters" : [
    //         {
    //             "_id" : ObjectId("619fe1d53810a130207a409d"),
    //             "age" : "25",
    //             "name" : "Raghu",
    //         }
    //     ],
    // }

    const oldCharacterObj = dramaDocWithSpecificCharacter.characters[0];

    const mergedCharacterObjs = _.merge(oldCharacterObj, newCharacterObj);
    // _.merge() returns a deep merged object
    console.log(mergedCharacterObjs);
    // ↓↓↓ console would log ↓↓↓
    // {
    //   _id: 619fe1d53810a130207a409d,
    //   age: "26",
    //   name: "Dr. Raghu",
    // };

    Drama.updateOne(
      { _id: req.drama._id, "characters._id": characterId },
      {
        $set: {
          "characters.$": mergedCharacterObjs,
        },
      },
      function(err, foundlist) {
        if (err) {
          console.log(err);
        } else {
          console.log("Update completed");
        }
      }
    );
  }
);

// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")

Note: We can also use the native Object.assign() or (spread operator) to merge objects but the downside of it is that it doesn’t merge nested objects which could cause issues if you later decide to add nested objects without making changes for deep merge.

Deepanshu
  • 890
  • 5
  • 18
0

You can pass your payload or request body like this if we provide _id it will prevent update to nested document

"characters" : [ 
  {
    "_id" : "619fdac5a03c8b10d0b8b13c",
    "age" : "updated value",
    "name" : "updated value", 
  }, {
    "_id" : "619fe1d53810a130207a409d",
    "age" : "updated value",
    "name" : "updated value", 
  }, {
    "_id" : "619fe1d53810a130207a502v",
    "age" : "updated value",
    "name" : "updated value", 
  }
],

It works for me for bulk update in array object

Tyler2P
  • 2,324
  • 26
  • 22
  • 31