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

fix: Cast string to Boolean

Open QuanticPotatoes opened this issue 4 years ago • 8 comments

Hi, I use class-transformer on NestJS and a cast problem occured during the conversion of the request payload.

Example :

{
"page": "1",
"limit": "1",
"isAnnotate": "false"
}

We got :

{
page: 1,
limit: 1,
isAnnotate: true
}

The defining class is :

class ArticleQuery {
  page: number;

  limit: number;

  isAnnotate?: boolean
}

https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L96 When the plainToClass function was executed such as :

/*
* metatype => ArticleQuery
* value => { page: "1", limit: "1",  isAnnotate: "false" }
* transformOptions => { enableImplicitConversion: true }
* /
classTransformer.plainToClass(metatype, value, this.transformOptions);

After a little digging, i found the caused into the function transform : https://github.com/typestack/class-transformer/blob/develop/src/TransformOperationExecutor.ts#L108

    } else if (targetType === Boolean && !isMap) {
      if (value === null || value === undefined) return value;
      return Boolean(value);

So i made a patch locally to solve the issue :

    } else if (targetType === Boolean && !isMap) {
      if (value === null || value === undefined) return value;
      return value === "true";

Is it an attended behavior from the function to not convert a string "true"/"false" to a Boolean ?

Current version: 0.4.0

Best regards

QuanticPotatoes avatar Mar 04 '21 14:03 QuanticPotatoes

@QuanticPotatoes - will this fix be committed into next official version?

tferi99 avatar Mar 12 '21 23:03 tferi99

"false" is true ... In my opinion, this should not be casted, correct type or transform annotation should be used.

@Transform(({ obj }) => {
    return [true, 'enabled', 'true'].indexOf(obj.isAnnotate) > -1;
})

skrosoft avatar Mar 23 '21 00:03 skrosoft

"false" is true ... In my opinion, this should not be casted, correct type or transform annotation should be used.

@Transform(({ obj }) => {
    return [true, 'enabled', 'true'].indexOf(obj.isAnnotate) > -1;
})

Good Job!

I was looking for it! Is there any way to return an error if no "true" or "false" was sent? I think to accept any value (that not be true) as false is weird.

Other way could be use with class-validator:

  @Transform(({ value }) => {
    return [true, 'enabled', 'true'].indexOf(value) > -1;
  })
  @IsBooleanString()
  isUnlocked: boolean;

but just does not work for me and I don't have any idea about what is happening When I use it with "IsBooleanString" decorator, I just receive, 400 bad request that says "isUnlocked must be a boolean string".

I would throw a 400 BAD REQUEST exception error if no "true" or "false" was sent.

felinto-dev avatar Mar 28 '21 14:03 felinto-dev

"false" is true ... In my opinion, this should not be casted, correct type or transform annotation should be used.

@Transform(({ obj }) => {
    return [true, 'enabled', 'true'].indexOf(obj.isAnnotate) > -1;
})

Your statement is "false"

jacobdo2 avatar May 11 '21 13:05 jacobdo2

@Transform(({ value }) => { return [true, 'enabled', 'true'].indexOf(value) > -1; }) @IsBooleanString()

After transforming value is boolean, not boolean-string. That's why u get error. Either make @IsBoolean() or remove @Transform()

popovmi avatar Oct 05 '21 08:10 popovmi

I just want undefined not to be tranformed to false actually

zachzhao1984 avatar Nov 29 '21 05:11 zachzhao1984

Hey, guys. I'm in a similar place. Using @joanna-liana's answer, I've manage to work around like this:

// dto
@IsOptional()
@IsBoolean()
@Transform(({ value }) => {
  if (value === 'true') return true;
  if (value === 'false') return false;
  return value;
})
private: boolean;
// controller
@UsePipes(
  new ValidationPipe({
    always: true,
  }),
)

It's fishy, but it works.

andrebrito avatar May 12 '22 23:05 andrebrito

I think a good argument for why 'false' should be treated as false is that class transformer gets used for parsing using requests and if the request is a GET and not a post all values get converted into strings thus false becomes 'false'. I'm having this trouble when using nestjs and doing GET requests. I use the qs lib to convert my request object qs.stringify(requestObject) which in the end my 'false' becomes true.

zaptree avatar Aug 24 '23 13:08 zaptree

For me worked IsBooleanString decorator:

DTO:

@IsBooleanString()
@IsOptional()
isEnabled?: boolean

Controller:

@Get("/")
public async getEnabled(@Query(new ValidationPipe({ transform: true })) query: DTO) { ... }

dexat0r avatar Apr 18 '24 15:04 dexat0r

Duplicate of https://github.com/typestack/class-transformer/issues/550.

NoNameProvided avatar May 04 '24 11:05 NoNameProvided