class-validator icon indicating copy to clipboard operation
class-validator copied to clipboard

feature: Allow field whitelisting to be turned off for selected nested objects

Open juliusiv opened this issue 3 years ago • 2 comments
trafficstars

Description

I'd like to be able to turn off the whitelist option for nested objects.

If I have classes like:

class Dto {
    @IsObject()
    @ValidateNested()
    @Type(() => NestedDto)
    nested: NestedDto;
}

class NestedDto {
    @IsString()
    field: string;
}

I want to validate something like:

const object = {
    nested: { field: "nice", random: "other" }
}

I want object.nested.random === "other" after validation with whitelist=true. Today, that field will be undefined. I also want object.nested.field to still be validated as a string, and throw errors if it's not. In other words, I want to validate the defined fields in the nested object but still retain any undefined fields.

Proposed solution

I was thinking a whitelist argument could be added to the ValidationOptions in ValidateNested. If undefined, we just respect the option that the initial validate function was called with.

juliusiv avatar Jan 06 '22 18:01 juliusiv

Hello, is this a feature that was ever considered? I see the issue is still opened after more than 2 years

willbouch avatar Jul 16 '24 18:07 willbouch

@juliusiv @willbouch I think for nestJs, I found the best work around to achieve this by following this comment: https://github.com/nestjs/nest/issues/2390#issuecomment-517623971

I've updated it to also received global config options while initializing globally and update the validation options by following the pattern below:

// rewrite-validation-options.decorator.ts
import {
  ArgumentMetadata,
  Injectable,
  SetMetadata,
  ValidationPipe,
  ValidationPipeOptions,
} from '@nestjs/common';
import { ValidatorOptions } from 'class-validator';
import { Reflector } from '@nestjs/core';

export const REWRITE_VALIDATION_OPTIONS = 'rewrite_validation_options';

export function RewriteValidationOptions(options: ValidatorOptions) {
  return SetMetadata(REWRITE_VALIDATION_OPTIONS, options);
}

@Injectable()
export class UpdatableValidationPipe extends ValidationPipe {
  private readonly defaultValidatorOptions: ValidatorOptions;

  constructor(private reflector: Reflector, globalOptions: ValidationPipeOptions = {}) {
    super(globalOptions);
    // Store only class-validator relevant options
    this.defaultValidatorOptions = {
      whitelist: globalOptions.whitelist,
      forbidNonWhitelisted: globalOptions.forbidNonWhitelisted,
      skipMissingProperties: globalOptions.skipMissingProperties,
      forbidUnknownValues: globalOptions.forbidUnknownValues,
    };
  }

  async transform(value: any, metadata: ArgumentMetadata) {
    const overrideOptions = this.reflector.get<ValidatorOptions>(
      REWRITE_VALIDATION_OPTIONS,
      metadata.metatype
    );

    if (overrideOptions) {
      const originalOptions = { ...this.validatorOptions };
      this.validatorOptions = { ...this.defaultValidatorOptions, ...overrideOptions };

      try {
        const result = await super.transform(value, metadata);
        this.validatorOptions = originalOptions;
        return result;
      } catch (err) {
        this.validatorOptions = originalOptions;
        throw err;
      }
    }

    return super.transform(value, metadata);
  }
}

Then use it in the following way in the global interceptor:

  const reflector = app.get(Reflector);
  app.useGlobalPipes(new UpdatableValidationPipe(reflector, { whitelist: true, transform: true }));

And to use it in your DTO's do the following:

@RewriteValidationOptions({ whitelist: false })
export class MyDto {
  @ApiProperty({
.
.
.
}

I think this would be the best way forward since the NestJs team mentions they don't see any time soon implementing the feature here: https://github.com/nestjs/nest/issues/7779

GHkrishna avatar Jun 12 '25 19:06 GHkrishna