Валидация JSR 303. Если одно поле равно "чему-то", то эти другие поля не должны быть нулевыми
Я хочу сделать небольшую пользовательскую проверку с помощью JSR-303 javax.validation
,
У меня есть поле. И если в это поле введено определенное значение, я хочу потребовать, чтобы несколько других полей не null
,
Я пытаюсь понять это. Не уверен, что именно я бы назвал это, чтобы помочь найти объяснение.
Любая помощь будет оценена. Я довольно новичок в этом.
На данный момент я думаю о Custom Constraint. Но я не уверен, как проверить значение зависимого поля из аннотации. В основном я не уверен, как получить доступ к объекту панели из аннотации.
public class StatusValidator implements ConstraintValidator<NotNull, String> {
@Override
public void initialize(NotNull constraintAnnotation) {}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ("Canceled".equals(panel.status.getValue())) {
if (value != null) {
return true;
}
} else {
return false;
}
}
}
Это panel.status.getValue();
доставляет мне неприятности.. не знаю, как этого добиться.
5 ответов
В этом случае я предлагаю написать собственный валидатор, который будет проверять на уровне класса (чтобы мы могли получить доступ к полям объекта), что одно поле является обязательным, только если другое поле имеет определенное значение. Обратите внимание, что вы должны написать универсальный валидатор, который получает 2 имени поля и работает только с этими 2 полями. Чтобы требовать более одного поля, вы должны добавить этот валидатор для каждого поля.
Используйте следующий код в качестве идеи (я не проверял это).
Интерфейс валидатора
/** * Validates that field {@code dependFieldName} is not null if * field {@code fieldName} has value {@code fieldValue}. **/ @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class) @Documented public @interface NotNullIfAnotherFieldHasValue { String fieldName(); String fieldValue(); String dependFieldName(); String message() default "{NotNullIfAnotherFieldHasValue.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { NotNullIfAnotherFieldHasValue[] value(); } }
Реализация валидатора
/** * Implementation of {@link NotNullIfAnotherFieldHasValue} validator. **/ public class NotNullIfAnotherFieldHasValueValidator implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> { private String fieldName; private String expectedFieldValue; private String dependFieldName; @Override public void initialize(NotNullIfAnotherFieldHasValue annotation) { fieldName = annotation.fieldName(); expectedFieldValue = annotation.fieldValue(); dependFieldName = annotation.dependFieldName(); } @Override public boolean isValid(Object value, ConstraintValidatorContext ctx) { if (value == null) { return true; } try { String fieldValue = BeanUtils.getProperty(value, fieldName); String dependFieldValue = BeanUtils.getProperty(value, dependFieldName); if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) { ctx.disableDefaultConstraintViolation(); ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate()) .addNode(dependFieldName) .addConstraintViolation(); return false; } } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) { throw new RuntimeException(ex); } return true; } }
Пример использования валидатора
@NotNullIfAnotherFieldHasValue.List({ @NotNullIfAnotherFieldHasValue( fieldName = "status", fieldValue = "Canceled", dependFieldName = "fieldOne"), @NotNullIfAnotherFieldHasValue( fieldName = "status", fieldValue = "Canceled", dependFieldName = "fieldTwo") }) public class SampleBean { private String status; private String fieldOne; private String fieldTwo; // getters and setters omitted }
Обратите внимание, что реализация валидатора использует BeanUtils
класс от commons-beanutils
библиотека, но вы также можете использовать BeanWrapperImpl
от Spring Framework.
См. Также этот великолепный ответ: проверка поля с помощью Hibernate Validator (JSR 303)
Определите метод, который должен подтвердить значение true и поставить @AssertTrue
аннотация сверху:
@AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
Метод должен начинаться с "is".
Вы должны использовать обычай DefaultGroupSequenceProvider<T>
:
ConditionalValidation.java
// Marker interface
public interface ConditionalValidation {}
MyCustomFormSequenceProvider.java
public class MyCustomFormSequenceProvider
implements DefaultGroupSequenceProvider<MyCustomForm> {
@Override
public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {
List<Class<?>> sequence = new ArrayList<>();
// Apply all validation rules from ConditionalValidation group
// only if someField has given value
if ("some value".equals(myCustomForm.getSomeField())) {
sequence.add(ConditionalValidation.class);
}
// Apply all validation rules from default group
sequence.add(MyCustomForm.class);
return sequence;
}
}
MyCustomForm.java
@GroupSequenceProvider(MyCustomFormSequenceProvider.class)
public class MyCustomForm {
private String someField;
@NotEmpty(groups = ConditionalValidation.class)
private String fieldTwo;
@NotEmpty(groups = ConditionalValidation.class)
private String fieldThree;
@NotEmpty
private String fieldAlwaysValidated;
// getters, setters omitted
}
Смотрите также связанный вопрос по этой теме.
Вот мое мнение, постарался сделать его как можно более простым.
Интерфейс:
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = OneOfValidator.class)
@Documented
public @interface OneOf {
String message() default "{one.of.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
Валидационная реализация:
public class OneOfValidator implements ConstraintValidator<OneOf, Object> {
private String[] fields;
@Override
public void initialize(OneOf annotation) {
this.fields = annotation.value();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
int matches = countNumberOfMatches(wrapper);
if (matches > 1) {
setValidationErrorMessage(context, "one.of.too.many.matches.message");
return false;
} else if (matches == 0) {
setValidationErrorMessage(context, "one.of.no.matches.message");
return false;
}
return true;
}
private int countNumberOfMatches(BeanWrapper wrapper) {
int matches = 0;
for (String field : fields) {
Object value = wrapper.getPropertyValue(field);
boolean isPresent = detectOptionalValue(value);
if (value != null && isPresent) {
matches++;
}
}
return matches;
}
private boolean detectOptionalValue(Object value) {
if (value instanceof Optional) {
return ((Optional) value).isPresent();
}
return true;
}
private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("{" + template + "}")
.addConstraintViolation();
}
}
Использование:
@OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {
private StateType stateType;
private ModeType modeType;
}
Сообщения:
one.of.too.many.matches.message=Only one of the following fields can be specified: {value}
one.of.no.matches.message=Exactly one of the following fields must be specified: {value}
Другой подход заключается в создании (защищенного) метода получения, который возвращает объект, содержащий все зависимые поля. Пример:
public class MyBean {
protected String status;
protected String name;
@StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidator теперь может получить доступ к StatusAndSomething.status и StatusAndSomething.something и выполнить зависимую проверку.
Образец ниже:
package io.quee.sample.javax;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.Pattern;
import java.util.Set;
/**
* Created By [**Ibrahim Al-Tamimi **](https://www.linkedin.com/in/iloom/)
* Created At **Wednesday **23**, September 2020**
*/
@SpringBootApplication
public class SampleJavaXValidation implements CommandLineRunner {
private final Validator validator;
public SampleJavaXValidation(Validator validator) {
this.validator = validator;
}
public static void main(String[] args) {
SpringApplication.run(SampleJavaXValidation.class, args);
}
@Override
public void run(String... args) throws Exception {
Set<ConstraintViolation<SampleDataCls>> validate = validator.validate(new SampleDataCls(SampleTypes.TYPE_A, null, null));
System.out.println(validate);
}
public enum SampleTypes {
TYPE_A,
TYPE_B;
}
@Valid
public static class SampleDataCls {
private final SampleTypes type;
private final String valueA;
private final String valueB;
public SampleDataCls(SampleTypes type, String valueA, String valueB) {
this.type = type;
this.valueA = valueA;
this.valueB = valueB;
}
public SampleTypes getType() {
return type;
}
public String getValueA() {
return valueA;
}
public String getValueB() {
return valueB;
}
@Pattern(regexp = "TRUE")
public String getConditionalValueA() {
if (type.equals(SampleTypes.TYPE_A)) {
return valueA != null ? "TRUE" : "";
}
return "TRUE";
}
@Pattern(regexp = "TRUE")
public String getConditionalValueB() {
if (type.equals(SampleTypes.TYPE_B)) {
return valueB != null ? "TRUE" : "";
}
return "TRUE";
}
}
}