Repository: Automattic/mongoose (JavaScript)

Issue:
document.save corrupts complex Map fields of the document (with additionnal `$*` keys)
### Prerequisites

- [x] I have written a descriptive issue title
- [x] I have searched existing issues to ensure the bug has not already been reported


### Mongoose version

8.9.5

### Node.js version

22.13.1

### MongoDB server version

8.0.4

### Typescript version (if applicable)

_No response_

### Description

After upgrading from mongoose 7.8.6 to mongoose 8.9.5, modifying a Map field containing an array corrupts the Map with invalid '$*' keys in the Map.
Once the Map is corrupted, it cannot be read or modified with mongoose anymore.
See example below to reproduce.

Note that we also quickly tested it with a mongoDB version 6 and 7; the problem is still the same. It seems to be only related to the version of mongoose.

### Steps to Reproduce

In the example below. The field `test_map` if a map of an array of objects. It gets corrupted at step two of the code and we can observe this corruption at steps 3 and 4 of the code.

```js
const mongoose = require('mongoose');

// Schema
const test_collection_schema = new mongoose.Schema({
    name: {type: String, required: true},
    test_map: {
        type: Map,
        of: [{
            num: {type: Number, required: true},
            bool: {type: Boolean, required: true}
        }]
    }
});

// Mongo relation
const Test_collection = mongoose.model('Test_collection', test_collection_schema, 'test_collection');

run_test().catch((err) => console.error(err));

async function run_test () {
    // 0. Create and save a new document
    let doc1 = new Test_collection({name: 'name1', test_map: new Map()});
    await doc1.save();
    console.log('log #0', doc1.test_map);
    
    // 1. Refresh the document from the db
    doc1 = await Test_collection.findOne({_id: doc1._id});
    console.log('log #1', doc1.test_map);
    
    // 2. Modify the document (add a new key in the test_map map)
    doc1.test_map.set('key1', []);
    await doc1.save();
    console.log('log #2', doc1.test_map);
    
    // 3. Now, the document is wrong in the db and we cannot access the test_map anymore.
    doc1 = await Test_collection.findOne({_id: doc1._id});
    console.log('log #3', doc1.test_map);

    // 4. If get the object with .lean, we see that there is a problem in the db.
    //    The map contains an additionnal key '$*', which should not be present.
    doc1 = await Test_collection.findOne({_id: doc1._id}).lean();
    console.log('log #4', doc1.test_map);
};
```

Below is the resulting stdout (with mongoose 8.9.5):

```
log #0 Map(0) {}
log #1 Map(0) {}
log #2 Map(1) { 'key1' => [] }
log #3 undefined
log #4 { '$*': { key1: [] }, key1: [] }
```

### Expected Behavior

Here is the correct stdout that is expected (generated with mongoose 7.8.6):

```
log #0 Map(0) {}
log #1 Map(0) {}
log #2 Map(1) { 'key1' => [] }
log #3 Map(1) { 'key1' => [] }
log #4 { key1: [] }
```

In particular, we see that after step 2 of the code, the object is corrupted in the DB with an additional '$*' key in the Map. Such a key is not valid and makes further access to the map with mongoose fail.


Discussion:

Patch:
diff --git a/lib/helpers/document/applyDefaults.js b/lib/helpers/document/applyDefaults.js
index 258e570ec30..7d1b5b02382 100644
--- a/lib/helpers/document/applyDefaults.js
+++ b/lib/helpers/document/applyDefaults.js
@@ -19,6 +19,9 @@ module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildre
     const type = doc.$__schema.paths[p];
     const path = type.splitPath();
     const len = path.length;
+    if (path[len - 1] === '$*') {
+      continue;
+    }
     let included = false;
     let doc_ = doc._doc;
     for (let j = 0; j < len; ++j) {


Test patch:
diff --git a/test/types.map.test.js b/test/types.map.test.js
index c6486e507ab..a61850fa9a6 100644
--- a/test/types.map.test.js
+++ b/test/types.map.test.js
@@ -1150,4 +1150,33 @@ describe('Map', function() {
     const doc = await CarModel.findById(car._id);
     assert.deepStrictEqual(doc.owners.get('abc').toObject(), [{ name: 'Bill' }]);
   });
+
+  it('handles loading and modifying map of document arrays (gh-15196)', async function() {
+    const schema = new Schema({
+      name: { type: String, required: true },
+      test_map: {
+        type: Map,
+        of: [{
+          _id: false,
+          num: { type: Number, required: true },
+          bool: { type: Boolean, required: true }
+        }]
+      }
+    });
+    const Test = db.model('Test', schema);
+
+    let doc1 = new Test({ name: 'name1', test_map: new Map() });
+    await doc1.save();
+
+    doc1 = await Test.findOne({ _id: doc1._id });
+
+    doc1.test_map.set('key1', []);
+    await doc1.save();
+
+    doc1 = await Test.findOne({ _id: doc1._id });
+    assert.deepStrictEqual(doc1.toObject().test_map, new Map([['key1', []]]));
+
+    doc1 = await Test.findOne({ _id: doc1._id }).lean();
+    assert.deepStrictEqual(doc1.test_map, { key1: [] });
+  });
 });