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

后端 未结 10 2199
醉酒成梦
醉酒成梦 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: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), 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 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 {
        @JsonValue
        @Valid
        @NotNull
        @Size(min = 1, message = "array body must contain at least one item.")
        private List values;
    
        @JsonCreator
        public ValidList(E... items) {
            this.values = Arrays.asList(items);
        }
    
        public List getValues() {
            return values;
        }
    
        public void setValues(List 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 test(@Valid @RequestBody ValidList 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.SomeController.test(com.example.demo.ValidList): [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.SomeController.test(com.example.demo.ValidList) with 2 errors: [Field error in object 'validList' on field 'values[0].id': rejected value [-11]; co [...]"
    }
    

提交回复
热议问题