4

Let's say I have this schema:

var UserSchema = new Schema({
    name : {
        firstName : String,
        lastName : String
    }
});

And I create this user:

User.create({
    name : {
        firstName : Bob,
        lastName : Marley
    }
}, function (err, user) {
    if(err) {
        return console.error(err);
    } else {
        return console.log(user);
    }
});

I noticed that if I do this:

User.findOneAndUpdate({_id: userId}, { name: { firstName : "Damian" } }, function (err, user) {
    if(err) {
        return console.error(err);
    } else {
        return console.log(user);
    }
});

My user now is:

user = {
    name : {
        firstName : "Damian"
    }
}

However, if I do this:

User.findOneAndUpdate({_id: userId}, { "name.firstName" : "Damian" }, function (err, user) {
    if(err) {
        return console.error(err);
    } else {
        return console.log(user);
    }
});

My user is:

user = {
    name : {
        firstName : "Damian",
        lastName : "Marley"
    }
}

Is there a way to pass an Object with not all of its attributes filled out to findOneAndUpdate, and keep the attributes that were there before, without getting rid of them? (same functionality as $set in Mongo). This is really annoying...

bmpasini
  • 1,503
  • 1
  • 23
  • 43

2 Answers2

5

Flatten your incomplete nested object with flat, like this:

var flatten = require('flat')

flatten({
    name : {
        firstName : "Damian"
    }
})

// { 
//   'name.firstName': 'Damian'
// } 

And now you can call findOneAndUpdate exactly as you did in your second example.

Andrew Lavers
  • 8,023
  • 1
  • 33
  • 50
  • Oops, I actually tragically misunderstood your answer (as proposing removing one of the fields from schema...) and posted my own one - in the end actually using the same library. It's more elaborate, so I'm leaving it to be, but I'm not sure whether actually more helpful. +1 and sorry for duplicating your solution (by bad oversight). – bardzusny May 16 '15 at 20:52
  • Be very aware though that flattening might cause some weird things, like arrays in your data appearing as objects in your Mongo document. Consider an update like `{things: [1, 2]}`. Then `flatten({things: [1, 2]})` gives you `{ 'things.0': 1, 'things.1': 2 }` and `db.getCollection('...').updateOne({_id: ObjectId('...')}, {$set: { 'things.0': 1, 'things.1': 2 }})` will result in `things` (if it didn't exist already) being created as an object with keys '0' and '1', instead of an array. I found this out the painfully hard way. Use flat's `safe` option to prevent this. – SFG Mar 18 '21 at 17:48
4

If you pass actual full nested object to findOneAndUpdate() (or any other mongoose update method):

Model.findOneAndUpdate({ ... }, {
  name: {
    firstName : "Damian"
  }
})

You will overwrite the whole (nested) object, therefore removing all of its other properties from the document (in this case: lastName).

To update only specific properties of the nested object, not the whole object, you need to use full path to the nested properties.

{ "name.firstName" : "Damian" }

(this will leave lastName in the nested name object untouched)

But it can be annoying to take care of that manually, whenever you update nested object's properties.

Luckily, it's not a problem at all to flatten the update object you pass to findOneAndUpdate() - so it never ends up being more than one level deep.

There are many ways to do this, debated upon in this question: Fastest way to flatten / un-flatten nested JSON objects .

The quickest solution seems to be to use ready-made flat Node.js library.

var flatten = require('flat')

User.findOneAndUpdate(
  { _id: userId },
  // same as passing '{ "name.firstName": "Damian" }':
  flatten({
    name: {
      firstName : "Damian"
    }
  }), function (err, user) {
  if(err) {
    return console.error(err);
  } else {
    return console.log(user);
  }
});
Community
  • 1
  • 1
bardzusny
  • 3,788
  • 7
  • 30
  • 30