【问题标题】:@Valid when creating objects with jackson without controller@Valid 在使用没有控制器的杰克逊创建对象时
【发布时间】:2019-04-01 14:40:42
【问题描述】:

当从前端发送请求时,我在控制器中使用 @Valid 验证了一个模型:

@NotNull
@Size(min=1, message="Name should be at least 1 character.")
private String name;

@NotNull
@Pattern(regexp = "^https://github.com/.+/.+$", message = "Link to github should match https://github.com/USER/REPOSITORY")
private String github;

但现在我也在用 Jackson 的 ObjectMapper 创建一个没有控制器的对象。有没有办法在 ObjectMapper 中注册此验证,或者我应该只检查 setter 中的变量?

【问题讨论】:

标签: java spring jackson


【解决方案1】:

您可以扩展BeanDeserializer 并在反序列化后验证对象。要注册此 bean,请使用 SimpleModule

带有验证的简单 bean 反序列化器:

class BeanValidationDeserializer extends BeanDeserializer {

    private final static ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    private final Validator validator = factory.getValidator();

    public BeanValidationDeserializer(BeanDeserializerBase src) {
        super(src);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        Object instance = super.deserialize(p, ctxt);
        validate(instance);

        return instance;
    }

    private void validate(Object instance) {
        Set<ConstraintViolation<Object>> violations = validator.validate(instance);
        if (violations.size() > 0) {
            StringBuilder msg = new StringBuilder();
            msg.append("JSON object is not valid. Reasons (").append(violations.size()).append("): ");
            for (ConstraintViolation<Object> violation : violations) {
                msg.append(violation.getMessage()).append(", ");
            }
            throw new ConstraintViolationException(msg.toString(), violations);
        }
    }
}

我们可以如下使用它:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.io.File;
import java.io.IOException;
import java.util.Set;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        SimpleModule validationModule = new SimpleModule();
        validationModule.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
                if (deserializer instanceof BeanDeserializer) {
                    return new BeanValidationDeserializer((BeanDeserializer) deserializer);
                }

                return deserializer;
            }
        });

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(validationModule);

        System.out.println(mapper.readValue(jsonFile, Pojo.class));
    }
}

class Pojo {

    @NotNull
    @Size(min = 1, message = "Name should be at least 1 character.")
    private String name;

    @NotNull
    @Pattern(regexp = "^https://github.com/.+/.+$", message = "Link to github should match https://github.com/USER/REPOSITORY")
    private String github;

    // getters, setters, toString()
}

对于有效的JSON 有效载荷:

{
  "name": "Jackson",
  "github": "https://github.com/FasterXML/jackson-databind"
}

打印:

Pojo{name='Jackson', github='https://github.com/FasterXML/jackson-databind'}

对于无效的JSON有效载荷:

{
  "name": "",
  "github": "https://git-hub.com/FasterXML/jackson-databind"
}

打印:

Exception in thread "main" javax.validation.ConstraintViolationException: JSON object is not valid. Reasons (2): Name should be at least 1 character., Link to github should match https://github.com/USER/REPOSITORY, 
    at BeanValidationDeserializer.validate(JsonApp.java:110)
    at BeanValidationDeserializer.deserialize(JsonApp.java:97)

另见:

【讨论】:

    【解决方案2】:

    我还将发布我是如何做到的。创建实现验证器的类:

    public class UserValidator implements Validator {
    
        private static final int MINIMUM_NAME_LENGTH = 6;
    
        @Override
        public boolean supports(Class<?> clazz) {
            return User.class.isAssignableFrom(clazz);
        }
    
        public void validate(Object target, Errors errors) {
            ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "Name must be at least 7 characters long.");
            User foo = (User) target;
    
            if(foo.getGithub().length() > 0 && !extensionSpec.getGithub().matches("^(www|http:|https:)+//github.com/.+/.+$")){
                errors.rejectValue("github", "Github must match http://github.com/:user/:repo");
            }
    
            if (errors.getFieldErrorCount("name") == 0 && foo.getName().trim().length() < MINIMUM_NAME_LENGTH) {
                errors.rejectValue("name", "Name must be at least 7 characters");
            }
        }
    }
    

    然后使用反序列化的对象创建数据绑定器,获取绑定结果,然后验证对象:

    ObjectMapper mapper = new ObjectMapper();
    User foo = mapper.readValue(FooJson, User.class);
    
    Validator validator = new ObjectValidator();
    BindingResult bindingResult = new DataBinder(foo).getBindingResult();
    validator.validate(foo, bindingResult);
    
    if(bindingResult.hasErrors()){
        throw new BindException(bindingResult);
    }
    

    如果您想在响应正文中获取错误代码:

    @ExceptionHandler
    ResponseEntity handleBindException(BindException e){
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(e.getBindingResult().getAllErrors()
                        .stream()
                        .map(DefaultMessageSourceResolvable::getCode)
                        .toArray());
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-03
      • 1970-01-01
      • 1970-01-01
      • 2012-09-18
      • 1970-01-01
      • 2013-10-05
      • 1970-01-01
      • 2014-02-10
      相关资源
      最近更新 更多