Search code examples
springspring-mvcspring-bootspring-4http-request-parameters

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?


Solution

  • 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.