Search code examples
nestjsclass-validator

class-validator validate nested object based on parent properties NestJs


I want to get value of type property in class B for validate class A.

Class A is used as a property type for the property a in Class B.

class Content1 {}

class Content2 {}

class A {
  @IsValidContent()
  content: Content1 | Content2;
}

class B {
  @Type(() => A)
  @ValidateNested({ each: true })
  a: A;

  type: string;
}

export function IsValidContent(validationOptions?: ValidationOptions) {
  return (object: Object, propertyName: string) => {
    registerDecorator({
      name: "isValidContent",
      target: object.constructor,
      propertyName,
      constraints: [],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {

          const type = args.object["type"]; // how to get type properties from parent
          switch (type) {
            case "X":
              return isValidType(value, Content1);
            case "Y":
              return isValidType(value, Content2);
            default:
              return false;
          }
        },
      },
    });
  }
}

How to get type properties from class B in order to validate the property content in class A?

Validation decorator is IsValidContent


Solution

  • i have the same problem.

    I have created a validator that add parent object in each child:

    import { Exclude } from 'class-transformer';
    import {
      Validate,
      ValidationArguments,
      ValidationOptions,
      ValidatorConstraint,
      ValidatorConstraintInterface,
    } from 'class-validator';
    
    @ValidatorConstraint({ name: 'customValidator', async: false })
    export class AddParentRefConstraint implements ValidatorConstraintInterface {
      defaultMessage(): string {
        return '';
      }
    
      validate(
        obj: object,
        validationArguments: ValidationArguments,
      ): Promise<boolean> | boolean {
        // add the parent ref on obj
        const parentRef = new WeakRef(validationArguments.object);
        Object.defineProperty(obj, 'parentRef', {
          get: function () {
            return parentRef;
          },
        });
        Exclude()(obj, 'parentRef');
        return true;
      }
    }
    
    export function AddParentRef(validationOptions?: ValidationOptions) {
      return function (target: object, propertyKey: string | symbol) {
        return Validate(AddParentRefConstraint, validationOptions)(
          target,
          propertyKey,
        );
      };
    }
    

    A parentRef member is added in each object where the annotation is needed. Next, you can use it. I have using the @Exclude of class-transfomer on the property to ensure that the field if removed from serialisation, but if you you custom serialization, you should take care of it.

    Next you can do this:

    class Content1 {}
    
    class Content2 {}
    
    class A {
      @IsValidContent()
      content: Content1 | Content2;
    }
    
    class B {
      @AddParentRef()
      @Type(() => A)
      @ValidateNested({ each: true })
      a: A;
    
      type: string;
    }
    
    export function IsValidContent(validationOptions?: ValidationOptions) {
      return (object: Object, propertyName: string) => {
        registerDecorator({
          name: "isValidContent",
          target: object.constructor,
          propertyName,
          constraints: [],
          options: validationOptions,
          validator: {
            validate(value: any, args: ValidationArguments) {
    
              const type = args.object.parentRef["type"];
              switch (type) {
                case "X":
                  return isValidType(value, Content1);
                case "Y":
                  return isValidType(value, Content2);
                default:
                  return false;
              }
            },
          },
        });
      }
    }