Boolean parameter in request body is always true in NestJS api

11,283

Solution 1

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;
}

Solution 2

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.

Solution 3

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.

Share:
11,283
joe_inz
Author by

joe_inz

"No one in the brief history of computing has ever written a piece of perfect software. It's unlikely that you'll be the first." - Andy Hunt

Updated on July 21, 2022

Comments

  • joe_inz
    joe_inz almost 2 years

    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!!?