Spring MVC - @Valid on list of beans in REST service

后端 未结 10 2156
醉酒成梦
醉酒成梦 2020-11-29 23:05

In a Spring MVC REST service (json), I have a controller method like this one :

@RequestMapping(method = RequestMethod.POST, value = { \"/doesntmatter\" })
         


        
相关标签:
10条回答
  • 2020-11-29 23:39

    I think your best option is to wrap the list - How to validate request parameter if it is not a bean in spring MVC?

    There is no way atm to say that the @Valid applies to the elements of the collection.

    0 讨论(0)
  • 2020-11-29 23:43

    The only way i could find to do this is to wrap the list, this also means that the JSON input would have to change.

    @RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
    @ResponseBody
    public List<...> myMethod(@Valid @RequestBody List<MyBean> request, BindingResult bindingResult) {
    

    becomes:

    @RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
    @ResponseBody
    public List<...> myMethod(@Valid @RequestBody MyBeanList request, BindingResult bindingResult) {
    

    and we also need:

    import javax.validation.Valid;
    import java.util.List;
    
    public class MyBeanList {
    
        @Valid
        List<MyBean> list;
    
        //getters and setters....
    }
    

    This looks like it could also be possible with a custom validatior for lists but i have not got that far yet.

    The @Valid annotation is part of the standard JSR-303 Bean Validation API, and is not a Spring-specific construct. Spring MVC will validate a @Valid object after binding so-long as an appropriate Validator has been configured.

    Reference : http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html

    0 讨论(0)
  • 2020-11-29 23:46

    Given Spring-Boot + Jackson for JSON serialization + org.springframework.boot:spring-boot-starter-validation (must be included manually for spring boot >= 2.3.0)

    Using built-ins

    • add @Validated to your controller
    • use @Valid @NotNull @RequestBody List<@Valid Pojo> pojoList in your controller method signature

    This will throw a javax.validation.ConstraintViolationException error on invalid beans though, which is mapped to 500 Internal Error by default. Hence, ensure you have a ControllerAdvice for this as well !

    Using a wrapper

    A list wrapper is nice (that is, a class with a single field of type List<E>), but from the responses above you will have to change the JSON as well ({"list": []} vs []), which is not nice...

    Solution:

    • in the wrapper, use @JsonValue annotation on the wrapped list field
    • add a constructor taking a list as argument, and annotate it with @JsonCreator
    • in your controller method, use @Valid @RequestBody ListWrapper<Pojo> tokenBodies

    This works, is elegant, and doesn't require anything more. Moreover, it will throw the usual org.springframework.web.bind.MethodArgumentNotValidException on invalid beans.


    Wrapper Example (java):

    (For a full example in Kotlin, see https://stackoverflow.com/a/64060909)

    public class ValidList<E> {
        @JsonValue
        @Valid
        @NotNull
        @Size(min = 1, message = "array body must contain at least one item.")
        private List<E> values;
    
        @JsonCreator
        public ValidList(E... items) {
            this.values = Arrays.asList(items);
        }
    
        public List<E> getValues() {
            return values;
        }
    
        public void setValues(List<E> values) {
            this.values = values;
        }
    }
    
    public class SomePojo {
        @Min(value = 1)
        int id;
    
        @Size(min = 2, max = 32)
        String token;
    
        // getters and setters
    }
    
    @RestController
    public class SomeController {
    
        @PostMapping("/pojos")
        public ValidList<SomePojo> test(@Valid @RequestBody ValidList<SomePojo> pojos) {
            return pojos;
        }
    }
    

    Submit ok:

    curl -H "Content-Type: application/json" -X POST http://localhost:8080/pojos -d '[{"id": 11, "token": "something"}]'
    
    [{"token" : "something", "id" : 11}]
    

    Submit empty body:

    curl -H "Content-Type: application/json" -X POST http://localhost:8080/ns -d '[]'
    
    {
       "timestamp" : "2020-09-25T09:55:05.462+00:00",
       "error" : "Bad Request",
       "message" : "Validation failed for object='validList'. Error count: 1",
       "exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
       "path" : "/pojos",
       "status" : 400,
       "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.ValidList<com.example.demo.SomePojo> com.example.demo.SomeController.test(com.example.demo.ValidList<com.example.demo.SomePojo>): [Field error in object 'validList' on field 'values': rejected value [[]]; codes [Size.validList.values,Size.values,Size. [...]"
    }
    

    Submit invalid items:

    curl -H "Content-Type: application/json" -X POST http://localhost:8080/ns -d '[{"id": -11, "token": ""}]'
    
    {
       "timestamp" : "2020-09-25T09:53:56.226+00:00",
       "error" : "Bad Request",
       "message" : "Validation failed for object='validList'. Error count: 2",
       "exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
       "path" : "/pojos",
       "status" : 400,
       "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.ValidList<com.example.demo.SomePojo> com.example.demo.SomeController.test(com.example.demo.ValidList<com.example.demo.SomePojo>) with 2 errors: [Field error in object 'validList' on field 'values[0].id': rejected value [-11]; co [...]"
    }
    
    0 讨论(0)
  • 2020-11-29 23:48
    @Valid @RequestBody List<MyBean> request
    

    works for me so long as you submitting valid json:-

    [
        {
            "property1": "value1",
            "property2": "value2"
          },
        {
            "property1": "value3",
            "property2": "value4"
            }
    ]
    
    0 讨论(0)
提交回复
热议问题