Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boolean parameter in request body is always true in NestJS api

Consider this endpoint in my API:

@Post('/convert')
  @UseInterceptors(FileInterceptor('image'))
  convert(
    @UploadedFile() image: any,
    @Body(
      new ValidationPipe({
        validationError: {
          target: false,
        },
        // this is set to true so the validator will return a class-based payload
        transform: true,
        // this is set because the validator needs a tranformed payload into a class-based
        // object, otherwise nothing will be validated
        transformOptions: { enableImplicitConversion: true },
      }),
    )
    parameters: Parameters,
  ) {
    return this.converterService.start(image, parameters);
  }

The body of the request, which is set to parameters argument, contains a property called laserMode that should be a boolean type, it is validated like such on the parameters DTO:

  @IsDefined()
  @IsBoolean()
  public laserMode: boolean;

now the strange part, when a send a request from PostMan where:

  1. laserMode = false
  2. laserMode = cool (a string other the boolean value)

I noticed that laserMode is always set to true and this is after the validation process is completed because when I console.log the instance of Parameter in the constructor of the class

export class Parameters {
  ...
  constructor() {
    console.log('this :', this);
  }
  ...
}

I don't see the property!

Note: when laserMode is removed from the request, the expected validation errors are returned (should be defined, should be boolean value).

// the logged instance 'this' in the constructor
this : Parameters {
  toolDiameter: 1,
  sensitivity: 0.95,
  scaleAxes: 200,
  deepStep: -1,
  whiteZ: 0,
  blackZ: -2,
  safeZ: 2,
  workFeedRate: 3000,
  idleFeedRate: 1200,
  laserPowerOn: 'M04',
  laserPowerOff: 'M05',
  invest: Invest { x: false, y: true }
}
// the logged laserMode value in the endpoint handler in the controller
parameters.laserMode in controller : true
// the logged laser value from the service
parameters.laserMode in service : true
  • misspelling is checked
  • same result is noticed when using a Vue app instead of postman. So!!?
like image 689
joe_inz Avatar asked Nov 26 '19 08:11

joe_inz


5 Answers

This is how I got round the issue while managing to keep the boolean typing.

By referring to the original object by key instead of using the destructured value.

import { Transform } from 'class-transformer';

const ToBoolean = () => {
  const toPlain = Transform(
    ({ value }) => {
      return value;
    },
    {
      toPlainOnly: true,
    }
  );
  const toClass = (target: any, key: string) => {
    return Transform(
      ({ obj }) => {
        return valueToBoolean(obj[key]);
      },
      {
        toClassOnly: true,
      }
    )(target, key);
  };
  return function (target: any, key: string) {
    toPlain(target, key);
    toClass(target, key);
  };
};

const valueToBoolean = (value: any) => {
  if (value === null || value === undefined) {
    return undefined;
  }
  if (typeof value === 'boolean') {
    return value;
  }
  if (['true', 'on', 'yes', '1'].includes(value.toLowerCase())) {
    return true;
  }
  if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) {
    return false;
  }
  return undefined;
};

export { ToBoolean };
export class SomeClass {
  @ToBoolean()
  isSomething : boolean;
}
like image 99
David Kerr Avatar answered Oct 19 '22 18:10

David Kerr


Found a workaround for the issue with class-transformer

You can use this:

@IsBoolean()
@Transform(({ value} ) => value === 'true')
public laserMode: boolean;

This will transform the string into a boolean value, based on if it is 'true' or any other string. A simple workaround, but every string different than true, results in false.

like image 26
Marcelo Kochiyama Avatar answered Oct 19 '22 18:10

Marcelo Kochiyama


This is due to the option enableImplicitConversion. Apparently, all string values are interpreted as true, even the string 'false'.

There is an issue requesting a changed behavior for class-transformer.

like image 38
Kim Kern Avatar answered Oct 19 '22 20:10

Kim Kern


If you want to receive both true/false,
then use the below solution.
It will mark all true for defined values
and mark it as false for all others

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

Avoid using below decorators as they don't work well enough

@IsBoolean()  
@Type(() => Boolean)  
like image 2
NAVPREET SINGH Avatar answered Oct 19 '22 18:10

NAVPREET SINGH


did you find a permanent solution for this?
I solved it with this hack:

@IsBoolean()
@Transform(({ obj, key }) => obj[key] === 'true')
laserMode: boolean;
like image 2
Claudia Farias Avatar answered Oct 19 '22 18:10

Claudia Farias