Issue title: Save method converts nested undefined fields to null when the nested object isn't defined as a schema
 Issue body: **Do you want to request a *feature* or report a *bug*?**
Report a BUG

**What is the current behavior?**
When running `.set` with a nested field of value `undefined`, it's set to `null` in database.
Running the same update via `findOneAndUpdate` correctly sets the value to `undefined` (unsets it in database).
This only happens if the nested object doesn't use Schema.

**If the current behavior is a bug, please provide the steps to reproduce.**
```javascript
const mongoose = require("mongoose");

const nestedSchema = new mongoose.Schema({
  count: Number,
}, { _id: false });

const mySchema = new mongoose.Schema({
  name: String,
  // This will contain `null` values, if saved using `.save`
  nested: { count: Number },
  // This will always be correct (no `null` values)
  nestedSchema: nestedSchema,
});

const MyModel = mongoose.model("MyModel", mySchema);

async function main() {
  await mongoose.connect("mongodb://localhost:27017/test-mongoose");
  const instance1 = new MyModel({ name: "test1", nested: { count: 1 }, nestedSchema: { count: 1 } });
  const instance2 = new MyModel({ name: "test2", nested: { count: 2 }, nestedSchema: { count: 2 } });

  await Promise.all([instance1, instance2].map((instance) => instance.save()));

  const update = { nested: { count: undefined }, nestedSchema: { count: undefined } };
  instance1.set(update);

  await instance1.save();

  await MyModel.findOneAndUpdate({ _id: instance2._id }, { $set: update });
}

main()
  .then(() => process.exit())
  .catch((e) => {
    console.error(e);
    process.exit(1);
  });

```
Will result in this:
![image](https://user-images.githubusercontent.com/26621166/165923332-ad334804-a2f3-488b-be19-a860ae830eeb.png)

**What is the expected behavior?**
Giving an `undefined` value to `.set` method, should use `$unset` when updating the nested field.

Important note: This was how it worked before we updated to mongoose 6, I feel like it's related to the [removal of omitUndefined](https://mongoosejs.com/docs/migrating_to_6.html#removed-omitundefined) but the changelog doesn't mention `.set` method.

**What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.**
```
$ node -v                                                                                                                                                                                    
v12.22.7

$ yarn list --pattern mongoose                                                                                                                                                               
yarn list v1.22.18                                                                                                                                                                           
└─ moorechristian@example.com  

$ yarn list --pattern mongodb                                                                                                                                                                
yarn list v1.22.18                                                                                                                                                                           
├─ moorechristian@example.com                                                                                                                                                       
└─ moorechristian@example.com    

$ mongod --version
db version v5.0.7
```
 Comments: 
 Comment 0: After further investigation, it seems like the `save` method is the problem, after `set` the field still reports as `undefined`.

Adding a `console.log(...instance1.$__delta());` - this seems to be what `save` uses under the hood, it shows this:
```
{ _id: new ObjectId("626bb33db50dc8c9e8246498") } { '$set': { nested: { count: undefined } } }  
```
But looking at the definition, I would expect it to return an `$unset` operator
https://github.com/Automattic/mongoose/blob/f7fe2c7dae23e0702f554965e0055c5b27eadbfb/lib/model.js#L754-L774

EDIT: Assumption above is incorrect, since the `value` being check is the entire nested object `{ count: undefined }` in the broken case, so the if cannot catch it.
 Comment 1: Changing the `nested` field to its own schema seems to be a workaround.
```javascript
const nestedSchema = new mongoose.Schema({
  count: Number,
}, { _id: false });

const mySchema = new mongoose.Schema({
  name: String,
  nested: nestedSchema,
});
```

EDIT: I've updated the issue accordingly.
 Comment 2: Also to clarify why this is a problem, getting the model back from database and saving it would sometimes throw an error, because `null` wouldn't pass validation for `Number` - this is how we found out.
 Comment 3: Another huge issue when using `Schema` instead of `{}`, we now cannot use spread (`...`) on the nested object.

So something like `instance1.set({ nestedSchema: { ...instance1.nestedSchema, count: 1 }})` causes issues. In this case, it actually sets the entire `nestedSchema` property to `undefined`. 
Changing the spread to `...instance1.toObject().nestedSchema` works as intended, but we have this in so many places, refactoring isn't really possible (these cases would be hard to search for).
 Comment 4: ```javascript
const mongoose = require("mongoose");

const nestedSchema = new mongoose.Schema({
  count: Number,
}, { _id: false });

const mySchema = new mongoose.Schema({
  name: String,
  // This will contain `null` values, if saved using `.save`
  nested: { count: Number },
  // This will always be correct (no `null` values)
  nestedSchema: nestedSchema,
}, {minimize: false});

const MyModel = mongoose.model("MyModel", mySchema);

async function main() {
  await mongoose.connect("mongodb://localhost:27017/test-mongoose");
  await mongoose.connection.dropDatabase();
  const instance1 = new MyModel({ name: "test1", nested: { count: 1 }, nestedSchema: { count: 1 } });
  const instance2 = new MyModel({ name: "test2", nested: { count: 2 }, nestedSchema: { count: 2 } });

  await Promise.all([instance1, instance2].map((instance) => instance.save()));

  const update = { nested: { count: undefined }, nestedSchema: { count: undefined } };
  instance1.set(update);

  await instance1.save();

  await MyModel.findOneAndUpdate({ _id: instance2._id }, { $set: update });
  const items = await MyModel.find();
  console.log(items);
}

main()
  .then(() => process.exit())
  .catch((e) => {
    console.error(e);
    process.exit(1);
  });
```
 Comment 5: @tomaskallup the fix will be in 6.3.4.

On a related note: why are you doing `instance1.set({ nestedSchema: { ...instance1.nestedSchema, count: 1 }})` instead of just `instance1.set('nestedSchema.count', 1)` or `instance1.nestedSchema.count = 1` ? The extra `...instance1.nestedSchema` is unnecessary and expensive performance-wise: it causes an unnecessary cast on all of `nestedSchema` and makes Mongoose instantiate an entirely new subdocument, in addition to sending all properties in `nestedSchema` when you `save()`.
 Comment 6: > @tomaskallup the fix will be in 6.3.4.
> 
> 
> 
> On a related note: why are you doing `instance1.set({ nestedSchema: { ...instance1.nestedSchema, count: 1 }})` instead of just `instance1.set('nestedSchema.count', 1)` or `instance1.nestedSchema.count = 1` ? The extra `...instance1.nestedSchema` is unnecessary and expensive performance-wise: it causes an unnecessary cast on all of `nestedSchema` and makes Mongoose instantiate an entirely new subdocument, in addition to sending all properties in `nestedSchema` when you `save()`.

That's hard to say from the top of my head. We have quite a bit of old code which uses this, I'll have a look tomorrow and see if it can be avoided. It most likely can be, but my guess is it was just easier to use the spread because it's some sort of update endpoint and converting it from a single object to the dot notation would just take more time to implement.

We usually have something like this:
```typescript
// Update endpoint, which has validation on input
(ctx) => {
  const user = await User.findById(ctx.params.id)

  user.set({ contact: { ...user.contact, ...ctx.body }})
  
  // do some other stuff and save
}
```

Is there some other preferred way to do this kind of update, where we want to make sure it doesn't need to be change when the nested schema is updated?
Should we just convert the `ctx.body` to dot notation and run the update with that?
 Comment 7: `Object.assign(user.contact, ctx.body)` should work better. Or `user.set('contact', ctx.body, { merge: true })`.
{'source': 'GithubIssues', 'repo_name_with_owner': 'Automattic/mongoose', 'issue_id': 1220318181, 'repo_stars': 24932.0}