Cross field validation with Hibernate Validator (JSR 303)

前端 未结 15 1771
渐次进展
渐次进展 2020-11-22 02:37

Is there an implementation of (or third-party implementation for) cross field validation in Hibernate Validator 4.x? If not, what is the cleanest way to implement a cross fi

相关标签:
15条回答
  • 2020-11-22 03:02

    If you’re using the Spring Framework then you can use the Spring Expression Language (SpEL) for that. I’ve wrote a small library that provides JSR-303 validator based on SpEL – it makes cross-field validations a breeze! Take a look at https://github.com/jirutka/validator-spring.

    This will validate length and equality of the password fields.

    @SpELAssert(value = "pass.equals(passVerify)",
                message = "{validator.passwords_not_same}")
    public class MyBean {
    
        @Size(min = 6, max = 50)
        private String pass;
    
        private String passVerify;
    }
    

    You can also easily modify this to validate the password fields only when not both empty.

    @SpELAssert(value = "pass.equals(passVerify)",
                applyIf = "pass || passVerify",
                message = "{validator.passwords_not_same}")
    public class MyBean {
    
        @Size(min = 6, max = 50)
        private String pass;
    
        private String passVerify;
    }
    
    0 讨论(0)
  • 2020-11-22 03:04

    I don't have the reputation for commenting on the first answer but wanted to add that I have added unit tests for the winning answer and have the following observations:

    • If you get the first or field names wrong then you get a validation error as though the values don't match. Don't get tripped up by spelling mistakes e.g.

    @FieldMatch(first="invalidFieldName1", second="validFieldName2")

    • The validator will accept equivalent data types i.e. these will all pass with FieldMatch:

    private String stringField = "1";

    private Integer integerField = new Integer(1)

    private int intField = 1;

    • If the fields are of an object type which does not implement equals, the validation will fail.
    0 讨论(0)
  • 2020-11-22 03:05

    I'm surprised this isn't available out of the box. Anyway, here is a possible solution.

    I've created a class level validator, not the field level as described in the original question.

    Here is the annotation code:

    package com.moa.podium.util.constraints;
    
    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.RetentionPolicy.*;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy = MatchesValidator.class)
    @Documented
    public @interface Matches {
    
      String message() default "{com.moa.podium.util.constraints.matches}";
    
      Class<?>[] groups() default {};
    
      Class<? extends Payload>[] payload() default {};
    
      String field();
    
      String verifyField();
    }
    

    And the validator itself:

    package com.moa.podium.util.constraints;
    
    import org.mvel2.MVEL;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    public class MatchesValidator implements ConstraintValidator<Matches, Object> {
    
      private String field;
      private String verifyField;
    
    
      public void initialize(Matches constraintAnnotation) {
        this.field = constraintAnnotation.field();
        this.verifyField = constraintAnnotation.verifyField();
      }
    
      public boolean isValid(Object value, ConstraintValidatorContext context) {
        Object fieldObj = MVEL.getProperty(field, value);
        Object verifyFieldObj = MVEL.getProperty(verifyField, value);
    
        boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
    
        if (neitherSet) {
          return true;
        }
    
        boolean matches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
    
        if (!matches) {
          context.disableDefaultConstraintViolation();
          context.buildConstraintViolationWithTemplate("message")
              .addNode(verifyField)
              .addConstraintViolation();
        }
    
        return matches;
      }
    }
    

    Note that I've used MVEL to inspect the properties of the object being validated. This could be replaced with the standard reflection APIs or if it is a specific class you are validating, the accessor methods themselves.

    The @Matches annotation can then be used used on a bean as follows:

    @Matches(field="pass", verifyField="passRepeat")
    public class AccountCreateForm {
    
      @Size(min=6, max=50)
      private String pass;
      private String passRepeat;
    
      ...
    }
    

    As a disclaimer, I wrote this in the last 5 minutes, so I probably haven't ironed out all the bugs yet. I'll update the answer if anything goes wrong.

    0 讨论(0)
提交回复
热议问题