Spring Boot - 如何验证相互依赖的字段?

wma*_*ley 5 java spring-boot

Spring Boot 中是否有某种方法可以对依赖于彼此值的属性执行验证,并使错误消息与该属性关联?

我想以漂亮的 JSON 结构将错误返回给用户:

{
  "errors": {
    "name": "is required if flag is true"
  }
}
Run Code Online (Sandbox Code Playgroud)

例子:

@Entity
public class MyEntity {
  private boolean nameRequiredFlag;

  // Required if "nameRequiredFlag" is set to true:
  private String name;
}
Run Code Online (Sandbox Code Playgroud)

无法解决将错误消息与 name 属性关联的问题的一种解决方案是为实体创建验证器注释:

@ValidEntity
public class MyEntity {
  private boolean nameRequiredFlag;

  // Required if "nameRequiredFlag" is set to true:
  private String name;
}

@Constraint( validatedBy = { MyEntityValidator.class } )
@Documented
@Target( { ElementType.TYPE } )
@Retention( RetentionPolicy.RUNTIME )
public @interface ValidEntity{
  Class<?>[] groups () default {};

  String message () default "name is required if 'nameRequiredFlag' is true";

  Class<? extends Payload>[] payload () default {};
}


public class MyEntityValidator implements Validator<ValidEntity, MyEntity> {
  @Override
  public boolean isValid ( MyEntity entity, ConstraintValidatorContext context ) {
    if ( !entity.nameRequiredFlag ) return true;
    
    return !StringUtils.isBlank( entity.getName() );
  }
}
Run Code Online (Sandbox Code Playgroud)

这太麻烦了,而且不能解决我的问题。有什么办法可以通过框架验证来做到这一点吗?

编辑:这是针对 JSON API 的,消费者确实需要能够将错误消息与对哪个字段出现问题的最佳猜测相关联。向消费者发送整个对象或计算属性的错误消息是没有帮助的。

Inn*_*hef 9

@EvicKhaosKat 给出的解决方案是一种方法。然而,当有太多字​​段以复杂的方式相互依赖时,你的类就会充满注释,而我个人在将它们关联起来时遇到了很多困难。

一种更简单的方法是在 pojo 中创建一个方法,该方法执行跨字段验证并返回布尔值。在此方法的顶部用 进行注释@AssertTrue(message = "your message")。它将以更干净的方式解决您的问题。

public class SampleClass {

    private String duration;
    private String week;
    private String month;

    @AssertTrue(message = "Duration and time attributes are not properly populated")
    public boolean isDurationCorrect() {
        if (this.duration.equalsIgnoreCase("month")) {
            if (Arrays.asList("jan", "feb", "mar").contains(month))
                return true;
        }

        if (this.duration.equalsIgnoreCase("week")) {
            if (Arrays.asList("1-7", "8-15", "16-24", "25-31").contains(week))
                return true;
        }

        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:我尚未测试此代码,但已在多个地方使用此方法并且它有效。


Rom*_*Kat 2

可能的原因是name验证对尚未完全构造的对象进行操作,因此nameRequiredFlag尚未填充。

作为一个选项,有一个@GroupSequence注释,它允许按照您指定的顺序分组和执行验证。例如,可以添加MyEntity注释:

@ValidEntity(groups = DependentValidations.class)
@GroupSequence({MyEntity.class, DependentValidations.class})
Run Code Online (Sandbox Code Playgroud)

因此,类上的所有其他验证注释MyEntity将首先执行,然后在该DependentValidations组之后执行,该组由ValidEntity. 因此ValidEntity将在完全创建的对象上调用,并且按顺序是最后一个。

DependentValidations.class- 只是在附近某处创建的空界面,就像任何其他标记界面一样)

https://www.baeldung.com/javax-validation-groups可能会更详细地描述这一点。

ps @Innovationchef 提供的答案可能更适合这种情况:)