-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
Prerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure the bug has not already been reported
Mongoose version
8.14.0
Node.js version
22.13.1
MongoDB server version
"mongodb-memory-server": "^10.1.4"
Typescript version (if applicable)
5.8.2
Description
I'm attempting to migrate a time field stored as a primitive string (e.g., "12:34"
) to a subdocument with hours
and minutes
keys. This transformation needs to apply to multiple fields in a dynamic, reusable way. So there cant be a getter
or init
hook on the main document.
I tried two approaches:
Using a get
function on the schema field.
Adding a pre("init")
hook within the subdocument schema.
However, neither strategy correctly transforms the old string value. It seems the validator strips the string before the getter or hook is executed.
Steps to Reproduce
- Create and save a model with a string time field.
const oldUserSchema = new Schema<{ unknownKey: string }>(
{
unknownKey: {
type: String,
required: true,
},
},
);
const UserModelName = "User";
const OldUser = mongoose.model(UserModelName, oldUserSchema);
await OldUser.create({ unknownKey: "12:34" });
- Delete old model
mongoose.deleteModel(UserModelName);
- Redefine it using a subdocument
timeSchema
and attempt to transform the string value using a getter or pre("init") hook.
type Time = { hours: number; minutes: number };
const timeStringToObject = (time: string | Time): Time => {
if (typeof time !== "string") return time;
const [hours, minutes] = time.split(":");
return { hours: parseInt(hours), minutes: parseInt(minutes) };
};
const timeSchema = new Schema(
{
hours: { type: Number, required: true },
minutes: { type: Number, required: true },
},
);
const userSchema = new Schema<{ unknownKey: Time }>({
unknownKey: {
type: timeSchema,
get: timeStringToObject, // try getter
required: true,
},
});
// try also pre init hook
timeSchema.pre("init", function (rawDoc) {
console.log(rawDoc);
if (typeof rawDoc === "string") {
rawDoc = timeStringToObject(rawDoc);
}
});
const User = mongoose.model(UserModelName, userSchema);
- Query documents and inspect their output
await User.create({ unknownKey: { hours: 12, minutes: 35 } });
console.log(
await User.find(),
(await User.find()).map((doc) => doc.toObject({ getters: true }))
);
// [
// {
// _id: new ObjectId('6813958e24d105ccd4d1dac7'),
// __v: 0,
// id: '6813958e24d105ccd4d1dac7'
// },
// {
// _id: new ObjectId('6813958e24d105ccd4d1dac9'),
// unknownKey: { hours: 12, minutes: 35 },
// __v: 0,
// id: '6813958e24d105ccd4d1dac9'
// }
// ]
The string value is stripped by the validators before it can be picked up by getter
or init
hook.
Expected Behavior
I expected previously stored string values like "12:34" to be converted into { hours: 12, minutes: 34 } and not be stripped by validations.
Expected output:
// [
// {
// _id: new ObjectId('6813958e24d105ccd4d1dac7'),
// unknownKey: { hours: 12, minutes: 34 },
// __v: 0,
// id: '6813958e24d105ccd4d1dac7'
// },
// {
// _id: new ObjectId('6813958e24d105ccd4d1dac9'),
// unknownKey: { hours: 12, minutes: 35 },
// __v: 0,
// id: '6813958e24d105ccd4d1dac9'
// }
// ]