Previously I didn't declare the _id
in my Schema, so each new submission naturally will have MongoDB generated ObjectId
as it's _id
. However, the requirements have changed and now _id
is declared as String
as below.
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var MySchema = new Schema({
_id: {
type: String,
},
schoolID: {
type: mongoose.Schema.Types.ObjectId, ref: 'School'
},
points: {
type: Number
},
});
MySchema.index({ schoolID : 1})
module.exports = mongoose.model('Submission', MySchema);
However, now I cannot find my previously inserted documents using _id
at all. I tried
var submissionId = "60654319a8062f684ac8fde4"
Submission.findOne({ _id: mongoose.mongo.ObjectId(submissionId ) })
Submission.findOne({ _id: mongoose.Types.ObjectId(submissionId ) })
Submission.findOne({ _id: mongoose.ObjectId(submissionId ) })
but it will always return null
. So when I use var mongoose = require('mongoose').set('debug', true);
to check, it will display below; that all my queries above will still find using String
, and not ObjectId
Mongoose: submission.findOne({ _id: '60654319a8062f684ac8fde4' }, { projection: {} })
Problem - _id: { type: String,},
mongoose will cast the value before making a query to the database, so in your case, it will always be a string.
Convert your old objectId's to String as you're planning to use String _id
so it's better to keep it consistent.
Run these commands from the shell, Robomongo
This will add records with strings _id
for existing ObjectId
records.
db.collection.find({}).toArray() // loop all records
.forEach(function (c) {
if (typeof c._id !== 'string') { // check _id is not string
c._id = c._id.str; db.collection.save(c); // create new record with _id as string value
}
});
Delete records with ObjectId
db.collection.remove({ _id: { $type: 'objectId' } })
Add Mongoose custom types.
https://mongoosejs.com/docs/customschematypes.html
class StringOrObjectId extends mongoose.SchemaType {
constructor(key, options) {
super(key, options, 'StringOrObjectId');
}
convertToObjectId(v) {
const checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
let _val;
try {
if (checkForHexRegExp.test(v)) {
_val = mongoose.Types.ObjectId(v);
return _val;
}
} catch (e) {
console.log(e);
}
}
convertToString(v) {
let _val = v;
try {
if(_.isString(_val)) return _val;
if(_.isNumber(_val)) return _.toString(_val);
} catch (e) {
console.log(e);
}
}
cast(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;
var MySchema = new Schema({
_id: {
type: StringOrObjectId, // custom type here
},
schoolID: {
type: mongoose.Schema.Types.ObjectId, ref: 'School'
},
points: {
type: Number
},
});
Query
Submission.findOne({ _id: submissionId }); // it will cast ObjectId or String or throw error
Drawbacks
60516ae1ef682d2804a2fa72
is like valid ObjectId
it will convert to ObjectId
which will not match the record.Note - It's a rough class StringOrObjectId
add proper checks and test properly.
Easy way - Use mongoose.Mixed
https://mongoosejs.com/docs/schematypes.html#mixed
https://mongoosejs.com/docs/api.html#mongoose_Mongoose-Mixed
cont MySchema = new Schema({
_id: {
type: mongoose.Mixed,
},
schoolID: {
type: mongoose.Schema.Types.ObjectId, ref: 'School'
},
points: {
type: Number
},
});