Post Nested Object to Spring MVC controller using JSON Post Nested Object to Spring MVC controller using JSON ajax ajax

Post Nested Object to Spring MVC controller using JSON


Update: since Spring 3.1, it's possible to use @Valid On @RequestBody Controller Method Arguments.

@RequestMapping(value="/ajax/saveVendor.do", method = RequestMethod.POST)public @ResponseBody AjaxResponse saveVendor( @Valid @RequestBody UIVendor vendor,                                              BindingResult result,                                              Locale currentLocale )

After much trial and error, I've finally figured out, as well as I can, what the problem is. When using the following controller method signature:

@RequestMapping(value="/ajax/saveVendor.do", method = RequestMethod.POST)public @ResponseBody AjaxResponse saveVendor( @Valid UIVendor vendor,                                              BindingResult result,                                              Locale currentLocale )

The client script has to pass the field in the object in post-data (typically "application/x-www-form-urlencoded") format (i.e., field=value&field2=value2). This is done in jQuery like this:

$.post( "mycontroller.do", $.param(object), callback, "json" )

This works fine for simple POJO objects that don't have child objects or collections, but once you introduce significant complexity to the object being passed, the notation used by jQuery to serialize the object data is not recognized by Spring's mapping logic:

object[0][field]

The way that I solved this problem was to change the method signature in the controller to:

@RequestMapping(value="/ajax/saveVendor.do", method = RequestMethod.POST)public @ResponseBody AjaxResponse saveVendor( @RequestBody UIVendor vendor,                                              Locale currentLocale )

And change the call from client to:

    $.ajax(            {              url:"ajax/mycontroller.do",               type: "POST",               data: JSON.stringify( objecdt ),               success: callback,               dataType: "json",              contentType: "application/json"            } );    

This requires the use of the JSON javascript library. It also forces the contentType to "application/json", which is what Spring expects when using the @RequestBody annotation, and serializes the object to a format that Jackson can deserialize into a valid object structure.

The only side effect is that now I have to handle my own object validation inside of the controller method, but that's relatively simple:

BindingResult result = new BeanPropertyBindingResult( object, "MyObject" );Validator validator = new MyObjectValidator();validator.validate( object, result );

If anyone has any suggestions to improve upon this process, I'm all ears.


first, sorry for my poor english

in spring, if the param name is like object[0][field], they will consider it as a class type like sub

public class Test {    private List<Map> field;    /**     * @return the field     */    public List<Map> getField() {        return field;    }    /**     * @param field the field to set     */    public void setField(List<Map> field) {        this.field = field;    }}

that's why the spring will throw an exception said something "is neither an array nor a List nor a Map".

only when the param name is object[0].field, spring will treat it as a class's field.

you could find the constants def in org.springframework.beans.PropertyAccessor

so my solution is write a new param plugin for jquery, like below:

(function($) {  // copy from jquery.js  var r20 = /%20/g,  rbracket = /\[\]$/;  $.extend({    customParam: function( a ) {      var s = [],        add = function( key, value ) {          // If value is a function, invoke it and return its value          value = jQuery.isFunction( value ) ? value() : value;          s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );        };      // If an array was passed in, assume that it is an array of form elements.      if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {        // Serialize the form elements        jQuery.each( a, function() {          add( this.name, this.value );        });      } else {        for ( var prefix in a ) {          buildParams( prefix, a[ prefix ], add );        }      }      // Return the resulting serialization      return s.join( "&" ).replace( r20, "+" );    }  });/* private method*/function buildParams( prefix, obj, add ) {  if ( jQuery.isArray( obj ) ) {    // Serialize array item.    jQuery.each( obj, function( i, v ) {      if (rbracket.test( prefix ) ) {        // Treat each array item as a scalar.        add( prefix, v );      } else {        buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, add );      }    });  } else if (obj != null && typeof obj === "object" ) {    // Serialize object item.    for ( var name in obj ) {      buildParams( prefix + "." + name, obj[ name ], add );    }  } else {    // Serialize scalar item.    add( prefix, obj );  }};})(jQuery);

actual I just change the code from

buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );

to

buildParams( prefix + "." + name, obj[ name ], add );

and use $.customParam instead of $.param when do ajax request.


You can try something like this:

vendor['emails[0].emailAddress'] = "abc123@abc.com";vendor['emails[0].flags'] = 3;vendor['emails[1].emailAddress'] = "xyz@abc.com";vendor['emails[1].flags'] = 3;

:)