typestack / class-transformer

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

Include @IsOptional values to the body if they are missing with default value #1632

Closed AndonMitev closed 2 months ago

AndonMitev commented 9 months ago

Having following DTO:

export class UserLoginRequestDto {
  @IsOptional()
  email: string;

  @IsOptional()
  password: string;
}

and passing outside body: { email: 'test@gmail.com' }, how to create pipe that will check keys which are missing from the dto and assign to them null so after pipe, body becomes:

body: { email: 'test@gmail.com', password: null }

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'; import { instanceToPlain, plainToClass } from 'class-transformer';

@Injectable() export class AugmentOptionalPropertiesPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { const { metatype } = metadata;

if (!metatype) {
  return value;
}

// Convert the incoming value to an instance of the DTO
const dtoInstance = plainToClass(metatype, value);
// Convert the DTO instance back to a plain object
const plainObj = instanceToPlain(dtoInstance, { excludeExtraneousValues: false });

// Assign null to undefined properties
for (const prop in plainObj) {
  if (plainObj[prop] === undefined) {
    plainObj[prop] = null;
  }
}

console.log(Object.entries(metatype.prototype));
Object.entries(metatype.prototype).forEach(([key, defaultValue]) => {
  if (dtoInstance[key] === undefined) {
    dtoInstance[key] = null; // Or use 'defaultValue' if you want a specific default value
  }
});

console.log('plainObj', plainObj);
return plainObj;

} }

However i'm not able to find easy way to iterate over the keys from the DTO model it self, any ideas

diffy0712 commented 2 months ago

The IsOptional decorator is not a class-transformer decorator so it will not have affect on the transformer.

I think you can use a custom Transformer to make this behavior using class-transformer:

export class UserLoginRequestDto {
  @Expose()
  @Transform(({value}) => value ?? null )
  email?: string;

  @Expose()
  @Transform(({value}) => value ?? null )
  password?: string;
}

I have added the ?: to the types so typescript will know that it might be undefined (this is just a type, will not have affect, but in strict ts it will work nicely) Also I have added a custom transformer to return null on undefined. Since the Transform only run when Exposed explicitly I have added the @Expose as well. (you can create your very on @NullOnPlainTransform decorator if you want to make it read nicely)

This is a way you can make this work using class-transformer.

The thing you tried to implement I think can work a bit better since, you do not have to add anything on your existing DTO-s. Just a note that you could use Object.getOwnPropertyNames as well to get the available keys and if it does not exists on your plain you can just set it to null.

This is a bit old question, so I assume you were already be able to make it work.

diffy0712 commented 2 months ago

Closing as answered. If you have any questions left feel free to comment on this issue.

github-actions[bot] commented 4 weeks 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.