Repository: Automattic/mongoose (JavaScript)

Issue:
toJSON options gets passed to implicitly created schemas, including transform
### 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

7.2.0+

### Node.js version

20.x

### MongoDB server version

6.x

### Typescript version (if applicable)

_No response_

### Description

A new feature was added (https://github.com/Automattic/mongoose/issues/13325) where `toJSON` and `toObject` options get passed to implicitly created schemas in a schema. I believe this was so that if you set `virtuals: true` and `getters: true` that those options would get passed. Another option gets passed that gives us unexpected results: `transform`. We use this `transform()` function to modify the document before it goes out. Before it was only being run for the top level schema, but now it gets run for every sub document.

### Steps to Reproduce

```js
import mongoose from 'mongoose'

const schema = new mongoose.Schema(
  {
    items: [{ key: String }]
  },
  {
    toJSON: {
      transform (doc, obj) {
        console.log(obj)
        return obj
      }
    }
  }
)
const MyModel = mongoose.model('MyModel', schema)
await mongoose.connect('mongodb://localhost:27017/testing')
const doc = await MyModel.create({
  items: [{ key: 'foobar' }]
})
doc.toJSON()
await mongoose.disconnect()
```

This will produce logs that look something like this:

```
{ key: 'foobar', _id: new ObjectId("[objectid]") }
{
  items: [ { key: 'foobar', _id: new ObjectId("[objectid]") } ],
  _id: new ObjectId("[objectid]"),
  __v: 0
}
```

### Expected Behavior

Expected behavior is that the defined transform function would only apply to the schema on which it is defined.

The workaround is to wrap all of the implicit schemas with `new mongoose.Schema()`.


Discussion:
```javascript
const mongoose = require('mongoose');

const schema = new mongoose.Schema(
  {
    items: [{ key: String }]
  },
  {
    toJSON: {
      transform (doc, obj) {
        console.log(obj)
        return obj
      }
    }
  }
)
const MyModel = mongoose.model('MyModel', schema)

async function run() {
  await mongoose.connect('mongodb://localhost:27017/testing')
  await mongoose.connection.dropDatabase();
  const doc = await MyModel.create({
    items: [{ key: 'foobar' }]
  })
  doc.toJSON()
  await mongoose.disconnect()
}

run();
```



Patch:
diff --git a/lib/schema.js b/lib/schema.js
index 78ac94dda43..852851b2265 100644
--- a/lib/schema.js
+++ b/lib/schema.js
@@ -748,10 +748,10 @@ Schema.prototype.add = function add(obj, prefix) {
           childSchemaOptions.strict = this._userProvidedOptions.strict;
         }
         if (this._userProvidedOptions.toObject != null) {
-          childSchemaOptions.toObject = this._userProvidedOptions.toObject;
+          childSchemaOptions.toObject = utils.omit(this._userProvidedOptions.toObject, ['transform']);
         }
         if (this._userProvidedOptions.toJSON != null) {
-          childSchemaOptions.toJSON = this._userProvidedOptions.toJSON;
+          childSchemaOptions.toJSON = utils.omit(this._userProvidedOptions.toJSON, ['transform']);
         }
 
         const _schema = new Schema(_typeDef, childSchemaOptions);
@@ -1346,10 +1346,10 @@ Schema.prototype.interpretAsType = function(path, obj, options) {
           childSchemaOptions.strictQuery = options.strictQuery;
         }
         if (options.hasOwnProperty('toObject')) {
-          childSchemaOptions.toObject = options.toObject;
+          childSchemaOptions.toObject = utils.omit(options.toObject, ['transform']);
         }
         if (options.hasOwnProperty('toJSON')) {
-          childSchemaOptions.toJSON = options.toJSON;
+          childSchemaOptions.toJSON = utils.omit(options.toJSON, ['transform']);
         }
 
         if (this._userProvidedOptions.hasOwnProperty('_id')) {


Test patch:
diff --git a/test/document.test.js b/test/document.test.js
index c3ffb08d93b..fb841a4f165 100644
--- a/test/document.test.js
+++ b/test/document.test.js
@@ -793,14 +793,21 @@ describe('document', function() {
       assert.strictEqual(myModel.toObject().foo, void 0);
     });
 
-    it('should propogate toObject to implicitly created schemas gh-13325', async function() {
+    it('should propogate toObject to implicitly created schemas (gh-13599) (gh-13325)', async function() {
+      const transformCalls = [];
       const userSchema = Schema({
         firstName: String,
         company: {
           type: { companyId: { type: Schema.Types.ObjectId }, companyName: String }
         }
       }, {
-        toObject: { virtuals: true }
+        toObject: {
+          virtuals: true,
+          transform(doc, ret) {
+            transformCalls.push(doc);
+            return ret;
+          }
+        }
       });
 
       userSchema.virtual('company.details').get(() => 42);
@@ -809,6 +816,8 @@ describe('document', function() {
       const user = new User({ firstName: 'test', company: { companyName: 'foo' } });
       const obj = user.toObject();
       assert.strictEqual(obj.company.details, 42);
+      assert.equal(transformCalls.length, 1);
+      assert.strictEqual(transformCalls[0], user);
     });
   });
 
@@ -996,7 +1005,8 @@ describe('document', function() {
       assert.equal(foundAlicJson.friends, undefined);
       assert.equal(foundAlicJson.name, 'Alic');
     });
-    it('should propogate toJSON to implicitly created schemas gh-13325', async function() {
+    it('should propogate toJSON to implicitly created schemas (gh-13599) (gh-13325)', async function() {
+      const transformCalls = [];
       const userSchema = Schema({
         firstName: String,
         company: {
@@ -1004,7 +1014,13 @@ describe('document', function() {
         }
       }, {
         id: false,
-        toJSON: { virtuals: true }
+        toJSON: {
+          virtuals: true,
+          transform(doc, ret) {
+            transformCalls.push(doc);
+            return ret;
+          }
+        }
       });
 
       userSchema.virtual('company.details').get(() => 'foo');
@@ -1016,6 +1032,8 @@ describe('document', function() {
       });
       const obj = doc.toJSON();
       assert.strictEqual(obj.company.details, 'foo');
+      assert.equal(transformCalls.length, 1);
+      assert.strictEqual(transformCalls[0], doc);
     });
   });