Class-transformer: question: object id is transformed into a completely different one

Created on 19 Oct 2020  路  5Comments  路  Source: typestack/class-transformer

Description

When the classToClass or plainToClass methods are used in conjunction with and ObjectId instance (using the latest version of the bson module) the object id becomes a completely different one

Minimal code-snippet showcasing the problem

npm install bson class-transformer
import { ObjectId } from 'bson';
import { plainToClass } from 'class-transformer';
import assert from 'assert';

class User {
  _id!: ObjectId;
}

const plainUser = { _id: new ObjectId() };
const user = plainToClass(User, plainUser);

assert(user._id.toHexString() === plainUser._id.toHexString());

Expected behavior

Assuming how this module works I expect user._id to be a different instance than plainUser._id but both sharing the same hexadecimal value

assert(user._id instanceof ObjectId);
assert(user._id !== plainUser._id);
assert(user._id.toHexString() === plainUser._id.toHexString());

Actual behavior

The id is converted into a completely different one

wontfix question

Most helpful comment

Hey @leon19, your question made me curious so I ran your test and indeed got something like :

Screen Shot 2020-10-20 at 13 25 30

I investigated a bit and the reason is :

  • each call to bson new ObjectId() is going to produce an instance with a different hex string
  • the way that class-transformer is going to handle the deserialization (it tends to create a new instance of the types it's going to meet while deserializing I think).

Long story short, you could do something like this to overcome this problem :

import { ObjectId } from 'bson';
import { plainToClass, Transform } from 'class-transformer';
import assert from 'assert';

class User {
  // notice how I do not use the first parameter,
  //  which would provide the new instance created by `class-transformer` (and wrong hex string)
  // second parameter gives access to the original object being deserialized instead
  @Transform((_, from: any) => from._id, { toClassOnly: true })
  _id!: ObjectId;
}

const plainUser = { _id: new ObjectId() };
const user = plainToClass(User, plainUser);

assert(user._id.toHexString() === plainUser._id.toHexString());

I made a sample repo covering different cases with tests here, if you want to have deeper look.

Hope this helps 馃槂

All 5 comments

Hey @leon19, your question made me curious so I ran your test and indeed got something like :

Screen Shot 2020-10-20 at 13 25 30

I investigated a bit and the reason is :

  • each call to bson new ObjectId() is going to produce an instance with a different hex string
  • the way that class-transformer is going to handle the deserialization (it tends to create a new instance of the types it's going to meet while deserializing I think).

Long story short, you could do something like this to overcome this problem :

import { ObjectId } from 'bson';
import { plainToClass, Transform } from 'class-transformer';
import assert from 'assert';

class User {
  // notice how I do not use the first parameter,
  //  which would provide the new instance created by `class-transformer` (and wrong hex string)
  // second parameter gives access to the original object being deserialized instead
  @Transform((_, from: any) => from._id, { toClassOnly: true })
  _id!: ObjectId;
}

const plainUser = { _id: new ObjectId() };
const user = plainToClass(User, plainUser);

assert(user._id.toHexString() === plainUser._id.toHexString());

I made a sample repo covering different cases with tests here, if you want to have deeper look.

Hope this helps 馃槂

To improve upon @Roms1383 solution, you should create a new decorator so that you can capture the property name rather than hardcoding it to _id.

export const ExposeId = (options?: ExposeOptions) =>
  ((target: Object, propertyKey: string) =>  {
    Transform((_, obj) => obj[propertyKey])(target, propertyKey);
  });

Usage:

@Schema()
class Cat {
  @ExposeId()
  _id: Types.ObjectId;
}

This is the expected behavior. @Roms1383 and @Rush answer is the correct way to work around this. I will leave this issue open until this is documented.

This behavior has changed in 90feca36d2b44b6c66428ccef9402b13b58f7e2e. Functions and constructors are not called anymore when doing plain-to-class transformation.

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings