Skip to content

Inability to Migrate Primitive String Field to Subdocument Schema via get or init Hook #15389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks done
timheerwagen opened this issue May 1, 2025 · 0 comments
Open
2 tasks done

Comments

@timheerwagen
Copy link

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

  1. 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" });
  1. Delete old model
mongoose.deleteModel(UserModelName);
  1. 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);
  1. 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'
//   }
// ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant