I have a controller with the POST handler defined like so:
@RequestMapping(value=\"/ajax/saveVendor.do\", method = RequestMethod.POST)
public @ResponseBody A
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.
Define the field to be List
(interface), not ArrayList
(concrete type):
private List emailAddresses = new ArrayList();
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.
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;
:)