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