class-transformer icon indicating copy to clipboard operation
class-transformer copied to clipboard

fix: Transform `value` and `obj.key` are different for mongodb 4.x / mongoose 6.0

Open hasezoey opened this issue 4 years ago • 2 comments

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 install
  • yarn run run:directly (to ignore typescript errors) [yes, its run with ts-node, but same behavior when using tsc (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

hasezoey avatar Aug 13 '21 12:08 hasezoey

Is there any fix for this?

iamchathu avatar Feb 17 '24 19:02 iamchathu

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)

hasezoey avatar Feb 18 '24 12:02 hasezoey