I'm receiving a java.util.List<Object[]>
via JSON-rpc as a JavaScript array as follows.
[
[1, 0.10, 1.00],
[2, 0.20, 2.00],
[3, 0.30, 3.00],
[4, 0.40, 4.00],
[5, 0.50, 5.00],
[6, 0.60, 6.00],
[7, 0.70, 7.00],
[8, 0.80, 8.00],
[9, 0.90, 9.00],
[10, 1.00, 10.00],
[11, 1.10, 11.00],
[12, 1.20, 12.00],
[13, 1.30, 13.00],
[14, 1.40, 14.00],
[15, 1.50, 15.00],
[16, 1.60, 16.00],
[17, 1.70, 17.00],
[18, 1.80, 18.00]
]
I need to pass this same array back to the server (with a little modification in the last dimension).
I use the following function to send back this array.
var request;
var timeout;
var itemsArray=[];
function insert()
{
if(!request)
{
var i=0;
$('input[name="txtCharge[]"]').each(function()
{
isNaN($(this).val())||$(this).val()===''?itemsArray[i][2]='':itemsArray[i][2]=eval(eval($(this).val()).toFixed(2));
i++;
});
request = $.ajax({
dataType:"json",
type: "POST",
data: JSON.stringify({jsonrpc:'2.0', method:'insertZoneCharges', id:'jsonrpc', params:[itemsArray]}),
contentType: "application/json-rpc; charset=utf-8",
url: "AddZoneChargeList",
success: function(response)
{
alert(response.result);
},
complete: function()
{
timeout = request = null;
},
error: function(request, status, error)
{
if(status!=="timeout"&&status!=="abort")
{
alert(status+" : "+error);
}
}
});
timeout2 = setTimeout(function() {
if(request)
{
request.abort();
alert("The request has been timed out.");
}
}, 300000);
}
}
The structure of the array itemsArray
after the loop is exactly the same as mentioned in the first snippet.
The method to be invoked by this jQuery function is as follows.
@Namespace("/admin_side")
@ResultPath("/WEB-INF/content")
@ParentPackage(value="json-default")
public final class ZoneCharge extends ActionSupport implements Serializable
{
private static final long serialVersionUID = 1L;
public ZoneCharge() {}
//This method is invoked by the given jQuery function.
//It should accept the array as a parameter of type List<Object[]> but it doesn't.
@SMDMethod
public String insertZoneCharges(@SMDMethodParameter(name="list")List<Object[]> list)
{
for(Object[]o:list)
{
System.out.println(o[0]+" : "+o[1]+" : "+o[2]);
}
return "The action completed successfully.";
}
@Action(value = "AddZoneChargeList",
results = {
@Result(name = ActionSupport.SUCCESS, type = "json", params = {"enableSMD", "true"})},
interceptorRefs = {
@InterceptorRef(value = "json", params = {"enableSMD", "true"})})
public String insertAction() throws Exception {
return ActionSupport.SUCCESS;
}
}
When an attempt is made to make a request, it causes the following exception to be thrown.
Feb 19, 2014 6:14:00 AM org.apache.struts2.json.rpc.RPCError error
SEVERE: Incompatible types for property insertZoneCharges
org.apache.struts2.json.JSONException: Incompatible types for property insertZoneCharges
at org.apache.struts2.json.JSONPopulator.convertToCollection(JSONPopulator.java:254)
at org.apache.struts2.json.JSONPopulator.convert(JSONPopulator.java:131)
at org.apache.struts2.json.JSONInterceptor.invoke(JSONInterceptor.java:242)
at org.apache.struts2.json.JSONInterceptor.intercept(JSONInterceptor.java:133)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)
at org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:562)
at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:99)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at filter.NoCacheFilter.doFilter(NoCacheFilter.java:25)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:105)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:125)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.channel.ChannelProcessingFilter.doFilter(ChannelProcessingFilter.java:144)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:119)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:1822)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
Why isn't that array is mapped to List<Object[]>
, when the same List<Object[]>
is correctly mapped to an array while receiving the response from the server (this is done by a separate jQuery function which is not covered in this question)?
EDIT 1:
The following line,
data: JSON.stringify({jsonrpc:'2.0', method:'insertZoneCharges', id:'jsonrpc', params:[itemsArray]})
in the given jQuery function corresponds to the following string.
{
"jsonrpc": "2.0",
"method": "insertZoneCharges",
"id": "jsonrpc",
"params": [
[
[1, 0.1, 1],
[2, 0.2, 2],
[3, 0.3, 3],
[4, 0.4, 4],
[5, 0.5, 5],
[6, 0.6, 6],
[7, 0.7, 7],
[8, 0.8, 8],
[9, 0.9, 9],
[10, 1, 10],
[11, 1.1, 11],
[12, 1.2, 12],
[13, 1.3, 13],
[14, 1.4, 14],
[15, 1.5, 15],
[16, 1.6, 16],
[17, 1.7, 17],
[18, 1.8, 18]
]
]
}
This appears valid and should correctly be mapped to java.util.List<Object[]>
without causing the exception.
EDIT 2:
This works when the generic type parameter is removed so that the list is simply List
(and not <List<Object[]>>
) or the parameter is made to be of type of List<List<Object>>
. Something like,
@SMDMethod
public String insertZoneCharges(@SMDMethodParameter(name="list")List<List<Object>> list)
{
for(List<Object> o:list)
{
System.out.println(o);
}
return "The action completed successfully.";
}
The loop displays the following output.
[1, 0.1, 1]
[2, 0.2, 2]
[3, 0.3, 3]
[4, 0.4, 4]
[5, 0.5, 5]
[6, 0.6, 6]
[7, 0.7, 7]
[8, 0.8, 8]
[9, 0.9, 9]
[10, 1, 10]
[11, 1.1, 11]
[12, 1.2, 12]
[13, 1.3, 13]
[14, 1.4, 14]
[15, 1.5, 15]
[16, 1.6, 16]
[17, 1.7, 17]
[18, 1.8, 18]
Looking at the output, the JSON array should correctly be mapped to List<Object[]>
but it doesn't happen.
EDIT 3: (not a substantial edit).
Although the actual array is invisible in the jQuery function given, this can simply be reproduced by the following jQuery function.
function insert()
{
var a=[[1, 2], [3, 4]]; //Array.
$.ajax({
dataType:"json",
type: "POST",
data: JSON.stringify({jsonrpc:'2.0', method:'insertZoneCharges', id:'jsonrpc', params:[a]}),
contentType: "application/json-rpc; charset=utf-8",
url: "AddZoneChargeList",
success: function(response)
{
alert(response.result);
},
complete: function()
{
//Do something.
},
error: function(request, status, error)
{
//Do something.
}
});
}
}
And the expected method parameter of the SMD method may either be java.util.List<Object[]>
or java.util.List<Long[]>
. In either case, it fails with the exception given.
Remaining one last thing. If there is a single dimensional array like (in JavaScript),
var a=[1, 2];
then this is correctly mapped to Long[]
(and so as to Object[]
) as an SMD method parameter.
So, now I don't see anymore where to look.
Use the source, Luke. If you look at the class throwing that exception, you'll find that it doesn't support List<anything[]>
. The relevant lines from org.apache.struts2.json.JSONPopulator.convertToCollection()
:
232 // create an object for each element
233 for (int j = 0; j < values.size(); j++) {
234 Object listValue = values.get(j);
235
236 if (itemClass.equals(Object.class)) {
237 // Object[]
238 newCollection.add(listValue);
239 } else if (isJSONPrimitive(itemClass)) {
240 // primitive array
241 newCollection.add(this.convertPrimitive(itemClass, listValue, accessor));
242 } else if (Map.class.isAssignableFrom(itemClass)) {
243 Object newObject = convertToMap(itemClass, itemType, listValue, accessor);
244 newCollection.add(newObject);
245 } else if (List.class.isAssignableFrom(itemClass)) {
246 Object newObject = convertToCollection(itemClass, itemType, listValue, accessor);
247 newCollection.add(newObject);
248 } else if (listValue instanceof Map) {
249 // array of beans
250 Object newObject = itemClass.newInstance();
251 this.populateObject(newObject, (Map) listValue);
252 newCollection.add(newObject);
253 } else
254 throw new JSONException("Incompatible types for property " + accessor.getName());
255 }
This code is adding items to the list, and falls through a series of type checks before deciding how to instantiate an object from the data. The comment on line 237 is misleading; it appears to have been copied and pasted from convertToArray
; all it does is add the current listValue to the list. Similarly the comment on line 249 about an array isn't creating one either. In fact, there's no code in here to handle an array, so it drops through to line 254 and throws the exception you see. The code appears to be missing a couple of lines, like this:
else if (itemClass.isArray()) {
Object newObject = convertToArray(itemClass, itemType, listValue, method);
newCollection.add(newObject);
...translating pretty directly from the code in convert()
for handling one dimensional arrays - which, as you noticed, work. You'd have to ask the struts developers if this is an oversight or deliberate. It does look like you're stuck with collections of collections.
You are mapping parameters incorrectly the correct mapping would be
@SMDMethod
public String insertZoneCharges(@SMDMethodParameter(name="list")Object[] list)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With