8
@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?

Benjamin M
  • 23,599
  • 32
  • 121
  • 201

2 Answers2

5

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.

Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
  • Wow! Thank you. That code looks really looks like sh*t, **but it works!!** `:)` – Benjamin M Feb 10 '15 at 16:21
  • Now I noticed, that this produces a lot of other errors `:(` Spring's `Pageable` won't work anymore, because `Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface`. Seems like all those modules won't get registered anymore automatically by Spring Boot. – Benjamin M Feb 11 '15 at 10:57
  • If it used `RelaxedDataBinder` then it would support case-insensitive enum conversion, but it doesn't. – OrangeDog Aug 25 '16 at 09:55
1
@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.

Radu
  • 2,022
  • 3
  • 17
  • 28