Mongo: Custom schema type support on _id field

Hello,

Feature(s) impacted

‘Get a record’ route, using the recordId, and possibly other routes using recordIds (not tested).

Observed behavior

We are using Forest Admin with a MongoDB database, and in some collections the ‘_id’ field identifying the documents are of two types : ‘String’ or ‘Mongo.Schema.Types.ObjectId’.

Initially, the mongoose model used by Forest admin was generated with a ‘_id’ field of type String, it works well to display all records with a String id, but if it’s an ‘ObjectId’, we have the following error:

I tried to use a custom schema type (mongo doc) on my _id field to take both String and ObjectId types into account (I based my code on this stackoverflow topic), but the result is still a “cannot be found” message.

Here is the code I’m using:

class StringOrObjectId extends Mongoose.SchemaType {
  constructor(key, options) {
    super(key, options, 'StringOrObjectId');
  }

  convertToObjectId(v) {...}

  convertToString(v) {...}

  cast(val) {
    console.log("Testing for schema ",val)
    const objectIdVal = this.convertToObjectId(val);
    if (objectIdVal) return objectIdVal;

    const stringVal = this.convertToString(val)
    if (stringVal) return stringVal;

    throw new Error('StringOrObjectId: ' + val +
        ' Nor string nor ObjectId');
  }
}

Mongoose.Schema.Types.StringOrObjectId = StringOrObjectId;

To debug the problem, I use a custom route to get a record, using a RecordGetter (forest admin doc).
Before calling recordGetter.get(recordId), I cast ‘recordId’ to its type, String or ObjectId.
I enabled them Mongoose debug so we can see the requests done by Forest:

  • With a String id:
Mongoose: myCollection.aggregate([ { '$match': { '$and': [ { _id: 'yoG9PXegwDgBGnjWc' } ] } }, { '$group': { _id: null, count: { '$sum': 1 } } }], {})
Mongoose: myCollection.findOne({ _id: 'yoG9PXegwDgBGnjWc' }, { projection: {} })
GET /forest/myCollection/yoG9PXegwDgBGnjWc?timezone=Europe%2FParis 200 144 - 523.165 ms
  • With an Object id:
Mongoose: myCollection.aggregate([ { '$match': { '$and': [ { _id: '627cc11fddf7e1c05344d70b' } ] } }, { '$group': { _id: null, count: { '$sum': 1 } } }], {})
GET /forest/myCollection/627cc11fddf7e1c05344d70b?timezone=Europe%2FParis 404 113 - 106.366 ms

I conclude that the aggregate call fails to find the right record, because the ObjectId that I give to recordGetter.get(recordId) is cast back to a String, so mongoose doesn’t find it. If I manually execute the aggregate request with an ObjectId, the requested record is found.
Note that if I use an ObjectId in my Schema definition ('_id': Mongoose.Schema.Types.ObjectId), the aggregate request is correct (the ObjectId isn’t casted to String) and the record is correctly loaded in ForestAdmin (but all records with a String ‘_id’ fail to load):

Mongoose: myCollection.aggregate([ { '$match': { '$and': [ { _id: 627cc11fddf7e1c05344d70b } ] } }, { '$group': { _id: null, count: { '$sum': 1 } } }], {})
Mongoose: myCollection.findOne({ _id: ObjectId("627cc11fddf7e1c05344d70b") }, { projection: {} })
GET /forest/myCollection/627cc11fddf7e1c05344d70b?timezone=Europe%2FParis 304 - - 95.351 ms

Expected behavior

Being able to use a custom schema type for the ‘_id’ field, and prevent RecordGetter.get(recordId) from casting the ObjectId given in parameter to a String object.

Context

  • Project name: -
  • Team name: -
  • Environment name: all environments
  • Agent type & version: forest-cli/2.6.3 with express/mongoose v8.6.7

Thanks for your help

Hello @Aymeric

I’ll set up a test project and put a couple breakpoints to see where the id is being casted and come back to you