Manspof
Manspof

Reputation: 357

custom-validator validate object keys

I build nestjs with a typescript project and I'm trying to validate filters query params. I want only filter keys and values can pass validation. example for good params: pass validation

any-endpoint?filters={"userIds"=["1","2","5"],"ages"=[25]}

any-endpoint?filters={"names"=["david","samuel"],"ages"=[21]}

example for bad params: failed validation

any-endpoint?filters={"blaBla"=["1","2","5"],"ages"=[25]} // here the key blaBla not one of the filters options

any-endpoint?filters={"names"=[1,2]} // here the values of names is not string, it should be string

my code

import { IsNumber, IsOptional,IsArray, IsString } from 'class-validator';

class FiltersOptionsDto {
  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  userIds?: string[];

  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  ages?: number[];

  @IsOptional()
  @IsArray()
  @IsNumber({}, { each: true })
  names?: string[];
}

export class AQueryDto {
  @IsOptional()
  // what to add here?
  filters?: FiltersOptionsDto;
}

how to do it?

Upvotes: 1

Views: 2509

Answers (2)

Jay McDoniel
Jay McDoniel

Reputation: 70432

All right, so if you really want to send a query in that format, you can use the @Transform() decorator from class-transformer and use plainToClass to transform the filters value. To do this though, you need a couple of things.

  1. The @ValidateNested() decorator is necessary to make sure that it can be validated as expected. Namely so the child object can be validated.

  2. The @Transform() decorator will need to be used. This is working for me

import { IsOptional, ValidateNested } from 'class-validator';
import { Transform, plainToClass } from 'class-transformer'
import { FiltersOptionsDto } from './filter.dto';

export class AQueryDto {
  @IsOptional()
  @ValidateNested()
  @Transform((value) =>  plainToClass(FiltersOptionsDto, JSON.parse(value.replace(/=/g, ':'))))
  filters?: FiltersOptionsDto;
}

Each = needs to be made to a : so that it is the correct JSON format (I also separated out the classes as I prefer one class per file). With the above, the value passes in to filters will be taken from a string value to a JSON (plain), and then from a plain to a class.

  1. You need to set forbidNonWhitelisted: true in your ValidationPipe. I've done this like so:
@Module({
  providers: [
    {
      provide: APP_PIPE,
      useValue: new ValidationPipe({ forbidNonWhitelisted: true })
    }
  ]
})
export class AppModule {}

(Along with the other providers and controllers of course). I also like to add transform: true so that it's no longer JSON or string, but an actual class.

Now you can pass in

?filters={"userIds"=["1","2","5"],"ages"=["25"]}

And it will succeed while trying to pass in

?filters={"blaBla"=["1","2","5"],"ages"=[25]}

It will fail.

Upvotes: 3

x00
x00

Reputation: 13843

Haven't tested it, but from the docs I figure this is what you need:

export class AQueryDto {
  @IsOptional()
  @ValidateNested() // +
  filters?: FiltersOptionsDto;
}

Upvotes: -1

Related Questions