finneycanhelp finneycanhelp - 4 months ago 24
JSON Question

@Valid JSON request with BindingResult causes IllegalStateException

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.

Ali Ali
Answer

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;
    }
}