Search code examples
nestjsclass-transformer

Convert stringified JSON to Object using class-transformer


There is a nest.js project, where in the request body we expect an object, one property of this object contains stringified JSON value. The idea is to convert this string to an object, validate it and pass to controller as an object ValidationPipe set up:

app.useGlobalPipes(
  new ValidationPipe({
   whitelist: true,
   transform: true,
  }),
);

DTO:

@Transform(parseJson, { toClassOnly: true })
@Type(() => AdditionalInfo)
@IsNotEmptyObject()
@ValidateNested()
additionalInfo: AdditionalInfo;

parseJson function

export function parseJson(options: {
  key: string;
  value: string;
  obj: string | Record<string, any>;
}): Record<string, any> {
  try {
    return JSON.parse(options.value);
  } catch (e) {
    throw new BadRequestException(`${options.key} contains invalid JSON `);
  }
}

For some reason in the controller the parsed value gets lost, and we receive an empty object.


Solution

  • Looks like @Transform works well with primitives only. Decided to create ParseJsonPipe and use it instead. Usage (in the controller):

    @Body('additionalInfo', new ParseJsonPipe(), new ValidationPipe(AdditionalInfoDto)) additionalInfo: AdditionalInfo,
    

    ParseJsonPipe:

    import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
    
    @Injectable()
    export class ParseJsonPipe implements PipeTransform<string, Record<string, any>> {
      transform(value: string, metadata: ArgumentMetadata): Record<string, any> {
        const propertyName = metadata.data;
        try {
          return JSON.parse(value);
        } catch (e) {
          throw new BadRequestException(`${propertyName} contains invalid JSON `);
        }
      }
    }
    

    ValidationPipe implements PipeTransform from @nestjs/common, transform function looks like that:

    async transform(value: any): Promise<any> {
        if (!this.metaType) { // AdditionalInfoDto
          return value;
        }
        const object = plainToClass(this.metaType, value);
        const errors = await validate(object);
        if (errors.length > 0) {
          const message = this.getErrorMessages(errors);
          throw new BadRequestException({ message });
        }
        return value;
    }