Search code examples
node.jstypescriptexpresstypeorm

Validate array of objects


I would like to validate an array of objects sent via Postman like that:

{
  pit_fee: [
  {
    range: [1,2]
    fee:25
  },
  {
    range: [3,4]
    fee:30
  },
}

This is my validation schema using class-validators:

export class PitFeeObject {
    @IsNumber({}, { each: true })
    range: number[];

    @IsNumber()
    fee: number;
}

export class CreateConfigSchema {
    @IsArray()
    @ValidateNested({ each: true })
    pit_fee: PitFeeObject[];
}

And this is my middleware function that takes the request body as input and validates it:

const validationMiddleware = (
    type: any,
    value: string | 'body' | 'query' | 'params' = 'body',
    skipMissingProperties = false,
    whitelist = true,
    forbidNonWhitelisted = true,
): RequestHandler => {
    return (req, res, next) => {
        validate(plainToInstance(type, req[value]), { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => {
            if (errors.length > 0) {
                const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', ');
                next(new HttpException(400, message));
            } else {
                next();
            }
        });
    };
};

export default validationMiddleware;

The problem is that the @ValidateNested() decorator doesn't put error.constraint like all other decorators do, but it gives error.children.children.constraint.

So I should divide my function with an if statement. If error.constraint it catches the constraint else I map error 2 times, find the constraint and catch it. But mapping 2 times is not a good practice as it may be 2 times or 3 or any other number that change as many nested objects I have inside my array.


Solution

  • I think you can recursively traverse the error object to find the constraints, regardless of the nesting level.

    Example

    const validationMiddleware = (
        type: any,
        value: string | 'body' | 'query' | 'params' = 'body',
        skipMissingProperties = false,
        whitelist = true,
        forbidNonWhitelisted = true,
    ): RequestHandler => {
        return (req, res, next) => {
            validate(plainToInstance(type, req[value]), { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => {
                if (errors.length > 0) {
                    const errorMessages = getErrorMessages(errors);
                    next(new HttpException(400, errorMessages.join(', '));
                } else {
                    next();
                }
            });
        };
    };
    
    function getErrorMessages(errors: ValidationError[]): string[] {
        const messages: string[] = [];
        for (const error of errors) {
            if (error.constraints) {
                messages.push(...Object.values(error.constraints));
            }
            if (error.children && error.children.length > 0) {
                messages.push(...getErrorMessages(error.children));
            }
        }
        return messages;
    }
    
    export default validationMiddleware;