typestack / class-transformer

Decorator-based transformation, serialization, and deserialization between objects and classes.
MIT License
6.82k stars 500 forks source link

@Type(() => Date) breaks custom @Transform for classToPlain #899

Open ehaynes99 opened 3 years ago

ehaynes99 commented 3 years ago

Description

When using @Type(() => Date) along with an @Transform, calling plainToClass appears to first run the custom transform, then convert that result back into a Date.

class Example {
  @Type(() => Date)
  @Transform(() => '2222-12-31', { toPlainOnly: true })
  timestamp!: Date;
}

const instance = plainToClass(Example, { timestamp: '2021-01-01' });
const t1 = instance.timestamp;
const t2 = classToPlain(instance).timestamp;

console.log('instance', t1, typeof t1, 'is date:', t1 instanceof Date);
console.log('plain obj', t2, typeof t2, 'is date:', t2 instanceof Date);

Expected behavior

instance 2021-01-01T00:00:00.000Z object is date: true
plain obj 2222-12-31 string is date: false

Actual behavior

instance 2021-01-01T00:00:00.000Z object is date: true
plain obj 2222-12-31T00:00:00.000Z object is date: true

This seems to be specific to Date, as doing similar with @Type(() => Number) does not exhibit this behavior.

chriswhite199 commented 2 years ago

Run into the same issue with 0.5.1 - the issue appears to be after the custom transform is applied (classToPlain), a final call is made to this.transform, which is passed the string value of your date, and then proceeds to pass that to the Date constructor to transform is back into a Date object:

chriswhite199 commented 2 years ago

So for 0.5.1, the solution appears to be remove the @Type decoration on the date field:

  // @Type(() => Date)
  // If you want to serialize as ISO8601 string, and deserialize from ISO8601 or ms or anything the Date constructor can handle
  @Transform(({ value }) => value && value.toISOString(), { toPlainOnly: true })
  @Transform(({ value }) => value && new Date(value), { toClassOnly: true })
  exp?: Date;
procaconsul commented 1 year ago

@chriswhite199 curiosly, @Transform with toPlainOnly doesn't seem to be called on instanceToPlain (as one would expect!) even following your suggestion :(

igrek8 commented 7 months ago

Struggling with @Transform and @Type.

import 'reflect-metadata';

import { Transform, Type, plainToInstance } from 'class-transformer';
import { IsString, ValidateNested } from 'class-validator';

class A {
  @IsString()
  name!: string;
}

class B {
  @Transform(({ value }) => JSON.parse(value))
  @Type(() => A)
  @ValidateNested()
  schema!: A;
}

const obj = plainToInstance(B, { schema: '{"name": "test"}' });
console.log(obj);

// Expected: B { schema: A { name: 'test' } }
// Received: B { schema: Object { name: 'test' } }