class-transformer
class-transformer copied to clipboard
fix: Transform `value` and `obj.key` are different for mongodb 4.x / mongoose 6.0
Description
when using Transform with mongoose 6.0 (6.0.0-rc1) and / or mongodb 4.x.x, the value and obj.key are different values
(Note: in this case key is for _id which is mongoose.Types.ObjectId instance)
Minimal code-snippet showcasing the problem
// NodeJS: 16.6.1
// MongoDB: 4.2-bionic (Docker)
import { getModelForClass, prop } from "@typegoose/typegoose"; // @typegoose/[email protected]
import * as mongoose from "mongoose"; // [email protected]
import { classToPlain, Exclude, Expose, plainToClass, Transform } from "class-transformer"; // [email protected]
// re-implement base Document to allow class-transformer to serialize/deserialize its properties
// This class is needed, otherwise "_id" and "__v" would be excluded from the output
class DocumentCT {
@Expose()
// makes sure that when deserializing from a Mongoose Object, ObjectId is serialized into a string
@Transform(
(value: any) => {
if ("value" in value) {
console.log("value", value);
if (value.value !== value.obj[value.key]) {
console.log("value and obj.key are different!");
}
return value.value.toString(); // because "toString" is also a wrapper for "toHexString"
}
return "unknown value";
},
{ toClassOnly: true }
)
public _id!: string;
@Expose()
public __v!: number;
}
@Exclude()
class Account extends DocumentCT {
@prop()
@Expose()
public email?: string;
@prop()
@Expose({ groups: ["admin"] })
public password?: string;
}
const AccountModel = getModelForClass(Account);
(async () => {
await mongoose.connect(`mongodb://localhost:27017/`, { dbName: "verifyMASTER" });
let id: string;
// Init
const { _id } = await AccountModel.create({
email: "[email protected]",
password: "secret",
} as Account);
// note here that _id is an ObjectId, hence the toString()
// otherwise it will have the shape of : { _bsonType: 'ObjectId', id: ArrayBuffer }
id = _id.toString();
// first test
console.log("saved", id);
// lean return a Plain Old Javascript Object
const pojo = await AccountModel.findById(id).orFail().lean().exec();
console.log("pojo", pojo, pojo._id.toString() === id);
// deserialize Plain Old Javascript Object into an instance of the Account class
// serialize Account instance back to a Plain Old Javascript Object, applying class-transformer's magic
const serialized = classToPlain(plainToClass(Account, pojo));
console.log("ctp", serialized, serialized._id === id);
// the reason for doing a transformation round-trip here
// is that class-transformer can only works it magic on an instance of a class with its annotation
console.log(serialized === {
_id: id,
__v: 0,
email: "[email protected]",
});
await mongoose.disconnect();
})();
Output
Note: some comments added, starting with #
yarn run v1.22.10
$ ts-node ./src/test.ts
saved 6116693687f80aa238917279
pojo {
_id: new ObjectId("6116693687f80aa238917279"),
email: '[email protected]',
password: 'secret',
__v: 0
} true
value {
value: new ObjectId("6116693687f80aa23891727c"), # this is different
key: '_id',
obj: {
_id: new ObjectId("6116693687f80aa238917279"), # with this
email: '[email protected]',
password: 'secret',
__v: 0
},
type: 0,
options: {
enableCircularCheck: false,
enableImplicitConversion: false,
excludeExtraneousValues: false,
excludePrefixes: undefined,
exposeDefaultValues: false,
exposeUnsetFields: true,
groups: undefined,
ignoreDecorators: false,
strategy: undefined,
targetMaps: undefined,
version: undefined
}
}
value and obj.key are different!
ctp {
_id: '6116693687f80aa23891727c',
__v: 0,
email: '[email protected]'
} false
false
Done in 2.94s.
Reproduction Repository & Branch: https://github.com/typegoose/typegoose-testing/tree/verifyCTandM6Issue
Note: the example is taken from typegoose's tests and minified for reproduction (the linked branch is where typegoose is using mongoose 6.0)
Steps:
- clone
- enter directory
yarn installyarn run run:directly(to ignore typescript errors) [yes, its run withts-node, but same behavior when usingtsc(also ignoring types errors]
Expected behavior
Expected that value and obj.key to be the same property
Actual behavior
value and obj.key are different
PS: this might also just be a question, i just dont know what pre-transformations are applied, i just noticed that the value are different
Is there any fix for this?
this project has not been updated since the creation of this issue, so there is no "official" fix, typegoose uses a workaround for _id, which would need to be repeated for all ObjectIds and likely does not apply to all use-cases (or maybe there is some kind of global transform, i dont know)