typestack / class-transformer

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

question: await for promises when using class to plain #277

Closed vicdup closed 3 years ago

vicdup commented 5 years ago

Hi there, we are using class-transformer with groups in nestJs. We are struggling on how to make class-transformer await a property when this property is exposed. Currently it result in a null object.

For instance in our entity "Booking" we have a property "Contact" defined as follow:

class Example {
  @ManyToOne(type => Contact, contact => contact.bookings, { eager: true })
  @JoinColumn()
  @Expose({ groups: ['vero'] })
  @Field(type => Contact)
  contact: Promise<Contact>;
}

when we use plain to class on a booking fetched with "findOne" of Typeorm it results in :

contact: {}
Petr0vi4 commented 5 years ago

Try to add Transform decorator:

@Transform(promise => promise.then(value => plainToClass(Contact, value)), { toClassOnly: true })
vicdup commented 5 years ago

Hi @Petr0vi4

I am not sure I understand your answer.

Transform as you suggested does not work because it is not awaited and return a promise.

I feel like we are doing something wrong here because this seems so trivial...

Petr0vi4 commented 5 years ago

You typed contact property as a Promise<Contact>, so I think that after plainToClass you should expect to receive a Promise, that will be resolved to object of class Contact if awaited.

creaux commented 4 years ago

Actually I feel this is similar kind of problem.

class SecondModel {
  @IsNumber()
  public get count(): Promise<number> {
    return promise.then((data) => data.count);
  }
}
....
class FirstModel {
  @IsDefined()
  @IsInstance(SecondModel, { each: true })
  @ValidateNested({ each: true })
  @Type(() => SecondModel)
  public readonly some!: SecondModel[];
}

I'm getting the following as result which point me on that the promise is note resolved.

firstModel.some === {} // which should be number of course

How to resolve that promise? It doesn't seem to me there is a way according documentation. Do I missing something?

cosinus84 commented 4 years ago

@creaux did you find the solution to your problem, I ended in the same place.

smecnarowski commented 4 years ago

same for me - i'm trying to transform plaintext password to crypted string but i'm receiving it as empty object

import { IsString, IsEmail, IsBoolean, IsOptional, IsNotEmpty } from 'class-validator';
import { Transform } from 'class-transformer';
import * as bcrypt from 'bcrypt';

export class CreateUserDto {
  @IsEmail()
  @Transform(username => username.toLowerCase())
  readonly username: string;

  @IsString()
  @IsNotEmpty()
  @Transform(async password => {
    const hash = await bcrypt.hash(password, 10);
    return hash;
  })
  readonly password: string;

  @IsString()
  readonly name: string;

  @IsOptional()
  @IsBoolean()
  readonly isAdmin: boolean;

  @IsOptional()
  @IsBoolean()
  readonly isManufacturer: boolean;
}
imSaharukh commented 4 years ago

same for me - i'm trying to transform plaintext password to crypted string but i'm receiving it as empty object

import { IsString, IsEmail, IsBoolean, IsOptional, IsNotEmpty } from 'class-validator';
import { Transform } from 'class-transformer';
import * as bcrypt from 'bcrypt';

export class CreateUserDto {
  @IsEmail()
  @Transform(username => username.toLowerCase())
  readonly username: string;

  @IsString()
  @IsNotEmpty()
  @Transform(async password => {
    const hash = await bcrypt.hash(password, 10);
    return hash;
  })
  readonly password: string;

  @IsString()
  readonly name: string;

  @IsOptional()
  @IsBoolean()
  readonly isAdmin: boolean;

  @IsOptional()
  @IsBoolean()
  readonly isManufacturer: boolean;
}

did you find any solution? I'm facing the same issue

namttdh commented 4 years ago

same as me, both

@Transform(async avatar => {
   await stuff hrere...
    return avatar_str;
  })
 public avatar;

or

@Expose({name: 'avatar'})
  async getAvatar() {
    await something;
    return `${process.env.BASE_URL}/file/image/${this.avatar}/avatar.jpg`;
  }

doesn't work. Only return object empty {};

smecnarowski commented 4 years ago

@imSaharukh solved only by workaround. I have consulted on discord with developers and there were no solution for this because those transformations doesn't handle promise based solutions. Finally I inserted code to the service class

import { Injectable, Inject } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { User } from './interfaces/user.interface';
import { Model } from 'mongoose';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {
  constructor(
    @Inject('USER_MODEL') 
    private userModel: Model<User>
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    createUserDto.password = await bcrypt.hash(createUserDto.password, 10)
    const created = new this.userModel(createUserDto)
    return created.save()
  }

  ...

}
iteratelance commented 4 years ago

I have this same issue and have yet to find a workaround. This seems so basic and fundamental.

NoNameProvided commented 3 years ago

This is not possible currently, I have added a tracking issue for this feature at #549. Please do not comment +1, waiting for this comments on it. Comment only if you have to add something to the conversation. To express your interest, you can hit the like emote on the issue.

github-actions[bot] commented 3 years ago

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.