Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customize Spring @RequestParam Deserialization for Maps and/or Nested Objects

@RestController
class MyController {
     @RequestMapping(...)
     public void test(Container container) { ... }
}

Spring by default uses Dot-Notation to deserialize a nested @RequestParam:

class Container {
    A a;
}

class A {
    String val;
}

works with:

http://.../myController?a.val=foo

But for Maps it uses Square Bracket notation:

class Container {
    Map<String, String> a;
}

works with:

http://.../myController?a[val]=foo

When using JavaScript there's of course no difference between a HashMap and a Nested Object, so everything will get serialized either with Dots or Square-Brackets.


Question:

How / where can I tell Spring (or Spring Boot if that's easier) to use Dot-Notation (or Square Brackets) for both, nested objects and Maps?

Or is there any reason why Spring makes a difference between those types?

like image 711
Benjamin M Avatar asked Feb 05 '15 16:02

Benjamin M


2 Answers

Spring Boot supports the use of dot-separated paths to bind maps thanks to its custom DataBinder subclass, RelaxedDataBinder. The good news is that its also a DataBinder that's used in Spring MVC to perform the request parameter binding. The bad news is that plugging in your own binder isn't straightforward and that it needs to be a WebDataBinder. You can plug one in by declaring your own RequestMappingHandlerAdapter bean named requestMappingHandlerAdapter. For example:

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdpter() {
    return new RequestMappingHandlerAdapter() {

        @Override
        protected InitBinderDataBinderFactory createDataBinderFactory(
                List<InvocableHandlerMethod> binderMethods)
                throws Exception {
            return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer()) {

                @Override
                protected ServletRequestDataBinder createBinderInstance(
                        final Object target, String objectName,
                        NativeWebRequest request) {

                    return new ServletRequestDataBinder(target) {

                        private RelaxedDataBinder relaxedBinder = new RelaxedDataBinder(target);

                        @Override
                        protected void doBind(MutablePropertyValues mpvs) {
                            this.relaxedBinder.bind(mpvs);
                        }
                    };
                }
            };
        }   
    };
}

You may well want to refactor this to avoid the use of multiple nested anonymous inner classes, but it hopefully illustrates the general approach.

like image 99
Andy Wilkinson Avatar answered Sep 17 '22 12:09

Andy Wilkinson


@InitBinder
private void initBinder(WebDataBinder binder, ServletRequest request) {
    new RelaxedDataBinder(binder.getTarget()).bind(new ServletRequestParameterPropertyValues(request));
}

This is how I got away with it; a method in the controller delegating to a RelaxedDataBinder.

like image 34
Radu Avatar answered Sep 17 '22 12:09

Radu