Hibernate Validator和Jackson:使用@JsonProperty值作为ConstraintViolation PropertyPath?

时间:2013-09-18 17:43:45

标签: jackson bean-validation hibernate-validator

说我有一个简单的POJO,如下面注释了Jackson 2.1和Hibernate Validator 4.3.1注释:

final public class Person {
  @JsonProperty("nm")
  @NotNull
  final public String name;

  public Person(String name) {
      this.name = name;
  }
}

我将这样的JSON发送到Web服务:

{"name": null}

报告时,Hibernate ConstraintViolation使用类成员标识符“name”而不是JsonProperty注释值。有谁知道是否可以让Hibernate Validator查看类的注释并使用该值代替?

3 个答案:

答案 0 :(得分:4)

不幸的是,没有简单的方法可以做到这一点。但是这里有一些可以帮助你的见解:

解析约束违规

ConstraintViolationException,您可以获得一组ConstraintViolation,它会公开约束违规上下文:

从属性路径中,您可以获取叶节点:

Path propertyPath = constraintViolation.getPropertyPath();
Optional<Path.Node> leafNodeOptional = 
        StreamSupport.stream(propertyPath.spliterator(), false).reduce((a, b) -> b);

然后检查节点的类型是否为PROPERTY并获取其名称:

String nodeName = null;

if (leafNodeOptional.isPresent()) {
    Path.Node leafNode = leafNodeOptional.get();
    if (ElementKind.PROPERTY == leafNode.getKind()) {
        nodeName = leafNode.getName();
    }
}

与杰克逊一起反思课程

要从叶子bean类中获取可用的JSON属性,您可以使用Jackson对其进行内省(请参阅此answer和此answer以获取更多详细信息):

Class<?> beanClass = constraintViolation.getLeafBean().getClass();
JavaType javaType = mapper.getTypeFactory().constructType(beanClass);

BeanDescription introspection = mapper.getSerializationConfig().introspect(javaType);
List<BeanPropertyDefinition> properties = introspection.findProperties();

然后通过将叶节点名称与Field中的BeanPropertyDefinition名称进行比较来过滤属性:

Optional<String> jsonProperty = properties.stream()
        .filter(property -> nodeName.equals(property.getField().getName()))
        .map(BeanPropertyDefinition::getName)
        .findFirst();

使用JAX-RS?

使用JAX-RS(如果您使用它),您可以定义ExceptionMapper来处理ConstraintViolationException s:

@Provider
public class ConstraintViolationExceptionMapper 
                 implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException exception) {
        ...
    }
}

要使用ObjectMapper中的ExceptionMapper,您可以为其提供ContextResolver<T>

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperContextResolver() {
        mapper = createObjectMapper();
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }

    private ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}

Providers中注入ExceptionMapper界面:

@Context
private Providers providers;

查找ContextResolver<T>,然后获取ObjectMapper实例:

ContextResolver<ObjectMapper> resolver = 
        providers.getContextResolver(ObjectMapper.class, MediaType.WILDCARD_TYPE);
ObjectMapper mapper = resolver.getContext(ObjectMapper.class);

如果您有兴趣获取@XxxParam名称,请参阅此answer

答案 1 :(得分:3)

不,那是不可能的。 Hibernate Validator 5(Bean Validation 1.1)具有ParameterNameProvider s的概念,如果违反了方法参数约束,则返回要报告的名称,但没有可比较的属性约束。

答案 2 :(得分:0)

当我使用 issue 模块进行验证时,我提出了这个 problem-spring-web,并且它不支持开箱即用的 bean 定义名称作为休眠。所以我想出了以下逻辑来覆盖 createViolationConstraintViolationAdviceTrait 并获取该字段的 JSONProperty 字段名称并再次创建违规。

public class CustomBeanValidationAdviceTrait implements ValidationAdviceTrait {

    private final ObjectMapper objectMapper;

    public CustomBeanValidationAdviceTrait(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }


 @Override
    public Violation createViolation(ConstraintViolation violation) {
        String propertyName = getPropertyName(violation.getRootBeanClass(), violation.getPropertyPath().toString());
        return new Violation(this.formatFieldName(propertyName), violation.getMessage());
    }


 private String getPropertyName(Class clazz, String defaultName) {
        JavaType type = objectMapper.constructType(clazz);
        BeanDescription desc = objectMapper.getSerializationConfig().introspect(type);
        return desc.findProperties()
                .stream()
                .filter(prop -> prop.getInternalName().equals(defaultName))
                .map(BeanPropertyDefinition::getName)
                .findFirst()
                .orElse(defaultName);
    }