I have a REST service which takes a JSON request. I want to validate the JSON request values that are coming in. How can I do that?
In Spring 3.1.0 RELEASE, I know one wants to make sure they are using the latest support classes listed at 3.1.13 New HandlerMethod-based Support Classes For Annotated Controller Processing
The old ones are items like: AnnotationMethodHandlerAdapter
. I want to make sure I am using the latest such as RequestMappingHandlerAdapter
.
This is because I hope it fixes an issue where I see this:
java.lang.IllegalStateException: Errors/BindingResult argument declared without preceding model attribute. Check your handler method signature!
My @Controller
handler method and associated code is this:
@Autowired FooValidator fooValidator;
@RequestMapping(value="/somepath/foo", method=RequestMethod.POST)
public @ResponseBody Map<String, String> fooBar(
@Valid @RequestBody Map<String, String> specificRequest,
BindingResult results) {
out("fooBar called");
// get vin from JSON (reportRequest)
return null;
}
@InitBinder("specificRequest") // possible to leave off for global behavior
protected void initBinder(WebDataBinder binder){
binder.setValidator(fooValidator);
}
FooValidator
looks like this:
@Component
public class FooValidator implements Validator {
public boolean supports(Class<?> clazz) {
out("supports called ");
return Map.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
out("validate called ");
}
private void out(String msg) {
System.out.println("****** " + getClass().getName() + ": " + msg);
}
}
If I remove the BindingResult
, everything works fine except I won't be able to tell if the JSON validated.
I am not strongly attached to the concept of using a Map<String, String>
for the JSON request or using a separate validator as opposed to a Custom Bean with validation annotation (How do you do that for a JSON request?). Whatever can validate the JSON request.
3.1.17 @Valid On @RequestBody Controller Method Arguments says that:
An
@RequestBody
method argument can be annotated with@Valid
to invoke automatic validation similar to the support for@ModelAttribute
method arguments. A resultingMethodArgumentNotValidException
is handled in theDefaultHandlerExceptionResolver
and results in a400
response code.
In other words, if you use @Valid @RequestBody
then Spring will reject an invalid request before it gets as far as calling your method. if you method is invoked, then you can assume the request body is valid.
BindingResult
is used for validation of form/command objects, rather than @RequestBody
.
I had to do something similar once. I just ended up making my life simpler by creating a Java object that the JSON could be convert into and used GSON
to do the conversion.
It was honestly as simple as:
@Autowired
private Gson gson;
@RequestMapping(value = "/path/info", method = RequestMethod.POST)
public String myMethod(@RequestParam(value = "data") String data,
Model model,
@Valid MyCustomObject myObj,
BindingResult result) {
//myObj does not contain any validation information.
//we are just using it as as bean to take advantage of the spring mvc framework.
//data contains the json string.
myObj = gson.fromJson(data, MyCustomObject.class);
//validate the object any way you want.
//Simplest approach would be to create your own custom validator
//to do this in Spring or even simpler would be just to do it manually here.
new MyCustomObjValidator().validate(myObj, result);
if (result.hasErrors()) {
return myErrorView;
}
return mySuccessView;
}
Do all your validation in your custom Validator
class:
public class MyCustomObjValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MyCustomObj.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
MyCustomObj c = (MyCustomObj) target;
Date startDate = c.getStartDate();
Date endDate = c.getEndDate();
if (startDate == null) {
errors.rejectValue("startDate", "validation.required");
}
if (endDate == null) {
errors.rejectValue("endDate", "validation.required");
}
if(startDate != null && endDate != null && endDate.before(startDate)){
errors.rejectValue("endDate", "validation.notbefore.startdate");
}
}
}
MyCustomObject
does not contain any annotation for validation, this is because otherwise Spring will try to validate this fields in this object which are currently empty because all the data is in the JSON String, it could for example be:
public class MyCustomObject implements Serializable {
private Date startDate;
private Date endDate;
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public void setStartDate(Date theDate) {
this.startDate = theDate;
}
public void setEndDate(Date theDate) {
this.endDate = theDate;
}
}
Try using the following:
@Autowired
private FooValidator fooValidator;
@InitBinder("specificRequest") // possible to leave off for global behavior
protected void initBinder(WebDataBinder binder){
binder.setValidator(fooValidator);
}
@ModelAttribute("specificRequest")
public Map<String, String> getModel() {
return new HashMap<String, String>();
}
This will make your controller serialize the request into the type you specify it to be. I have to say i normally dont make a service (autowired) of the validator, but it might be better.
Your handler looks like this now:
@RequestMapping(value="/somepath/foo", method=RequestMethod.POST)
public @ResponseBody Map<String, String> fooBar(
@Valid @ModelAttribute("specificRequest")
Map<String, String> specificRequest, BindingResult results) {
out("fooBar called");
// get vin from JSON (reportRequest)
return null;
}
To my knowledge this works perfectly and addresses the error you are receiving.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With