Class-validator: feat: add decorator to check if two properties match

Created on 9 Dec 2019  路  9Comments  路  Source: typestack/class-validator

First of all, thanks for the awesome validation solution! I use class-validator in my NestJS setup. Now I want to know if and how it is possible to check if two values match with eachother. Let's say I have a dto setup like this:

```user-create.dto.ts

export class UserCreateDto {
@IsString()
@IsNotEmpty()
firstName: string;

@IsEmail()
emailAddress: string;

@DoesMatch(o => o.emailAddress === o.emailAddressConfirm) // <---- check if email's match
@IsEmail()
emailAddressConfirm: string;
}
```

feature

Most helpful comment

Hi! I am posting there the solution I found to implement this control. It refers to my question/answer on StackOverflow. I am going to provide a suitable solution for this particular issue.

user-create.dto.ts

export class UserCreateDto {
   @IsString()
   @IsNotEmpty()
   firstName: string;

   @IsEmail()
   emailAddress: string;

   @Match('emailAddress')
   @IsEmail()
   emailAddressConfirm: string;
}

match.decorator.ts

import {registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator';

export function Match(property: string, validationOptions?: ValidationOptions) {
    return (object: any, propertyName: string) => {
        registerDecorator({
            target: object.constructor,
            propertyName,
            options: validationOptions,
            constraints: [property],
            validator: MatchConstraint,
        });
    };
}

@ValidatorConstraint({name: 'Match'})
export class MatchConstraint implements ValidatorConstraintInterface {

    validate(value: any, args: ValidationArguments) {
        const [relatedPropertyName] = args.constraints;
        const relatedValue = (args.object as any)[relatedPropertyName];
        return value === relatedValue;
    }

}

I managed to solve a similar problem on my personal project. Hope it helps!

All 9 comments

@vlapo

@vlapo how can we achieve something like this??

We do not have validator for this. Feel free open PR.

Hi guys, any update @vlapo?

Is there a way to make custom validator get access to other properties

Hi! I am posting there the solution I found to implement this control. It refers to my question/answer on StackOverflow. I am going to provide a suitable solution for this particular issue.

user-create.dto.ts

export class UserCreateDto {
   @IsString()
   @IsNotEmpty()
   firstName: string;

   @IsEmail()
   emailAddress: string;

   @Match('emailAddress')
   @IsEmail()
   emailAddressConfirm: string;
}

match.decorator.ts

import {registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator';

export function Match(property: string, validationOptions?: ValidationOptions) {
    return (object: any, propertyName: string) => {
        registerDecorator({
            target: object.constructor,
            propertyName,
            options: validationOptions,
            constraints: [property],
            validator: MatchConstraint,
        });
    };
}

@ValidatorConstraint({name: 'Match'})
export class MatchConstraint implements ValidatorConstraintInterface {

    validate(value: any, args: ValidationArguments) {
        const [relatedPropertyName] = args.constraints;
        const relatedValue = (args.object as any)[relatedPropertyName];
        return value === relatedValue;
    }

}

I managed to solve a similar problem on my personal project. Hope it helps!

Added a custom message to your solution like this:

@ValidatorConstraint({name: 'Match'})
export class MatchConstraint implements ValidatorConstraintInterface {

    validate(value: any, args: ValidationArguments) {
        const [relatedPropertyName] = args.constraints;
        const relatedValue = (args.object as any)[relatedPropertyName];
        return value === relatedValue;
    }

   defaultMessage(args: ValidationArguments) {
     const [relatedPropertyName] = args.constraints;
     return `${relatedPropertyName} and ${args.property} don't match`;
  }
}

force the property to exist on the class:

export function Match<K extends string, T extends { [$K in K]: any }>(
  property: K,
  validationOptions?: ValidationOptions,
) {
  return (object: T, propertyName: string) => {
    registerDecorator({
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [property],
      validator: MatchConstraint,
    });
  };
}

@mrpharderwijk and @pieromacaluso, it's works for me. Thanks!!

Full code:

import {registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator';

export function Match(property: string, validationOptions?: ValidationOptions) {
    return (object: any, propertyName: string) => {
        registerDecorator({
            target: object.constructor,
            propertyName,
            options: validationOptions,
            constraints: [property],
            validator: MatchConstraint,
        });
    };
}

@ValidatorConstraint({name: 'Match'})
export class MatchConstraint implements ValidatorConstraintInterface {

    validate(value: any, args: ValidationArguments) {
        const [relatedPropertyName] = args.constraints;
        const relatedValue = (args.object as any)[relatedPropertyName];
        return value === relatedValue;
    }

    defaultMessage(args: ValidationArguments) {
      const [relatedPropertyName] = args.constraints;
      return `${relatedPropertyName} and ${args.property} don't match`;
    }
}
Was this page helpful?
0 / 5 - 0 ratings