5

Question is pretty self explanatory. I want to send 2 different arrays of objects through a POST form without ajax to my controller.

I changed my question to using ajax and using a get request due to the size of the params. Currently getting a 400 (Bad Request). I have no idea why. Please take a look...

I have objects:

var phone = {phoneId:"", phoneNumber:"", phoneType:""};
var schedule = {scheduleId:"", time:"", day:""};

Which I place into a javascript arrays:

var phones = [phone1, phone2, phone3];
var schedules = [schedule1, schedule2];

and I use ajax to send:

var data = {
    index: id,
    schedules: schedules,
    phones: phones
}
var url = "/myController/myUrl"

$.getJSON(url, data, function(result){
    if(result.ok){
         $('#messageAlertSuccess').show();
    } else {
         $('#messageAlertError').show();    
    }
});

I created wrapping classes to map them like so:

public class PhoneWrapper(){
    private String phoneId;
    private String phoneNumber;
    private String phoneType;
}

And of course the scheduleWrapper follows the same convention.

Here's the method in my controller:

@ResponseBody
@RequestMapping(value="/myUrl", method=RequestMethod.GET)
public Result doSomething(@RequestParam("index") int index,
                          @RequestParam("phones") Set<PhoneWrapper> phoneWrappers,
                          @RequestParam("schedules") Set<ScheduleWrapper> scheduleWrappers,
                          Model model,
                          HttpSession session){

         //do stuff here.

}

I am currently getting a 400. So what's wrong?


Update: here's the url that the .getJSON jquery method is building:

http://localhost:8080/myApp/myController/myUrl?index=9&schedules%5B0%5D%5BscheduleId%5D=1&schedules%5B0%5D%5BfromDay%5D=Monday&schedules%5B0%5D%5BtoDay%5D=Friday&schedules%5B0%5D%5BfromTime%5D=08%3A30%3A00&schedules%5B0%5D%5BtoTime%5D=16%3A00%3A00&schedules%5B1%5D%5BscheduleId%5D=5&schedules%5B1%5D%5BfromDay%5D=Saturday&schedules%5B1%5D%5BtoDay%5D=Monday&schedules%5B1%5D%5BfromTime%5D=09%3A00%3A00&schedules%5B1%5D%5BtoTime%5D=13%3A00%3A00&phones%5B0%5D%5BphoneId%5D=6&phones%5B0%5D%5BphoneNumber%5D=787-788-1111&phones%5B0%5D%5BphoneType%5D=PHONE&phones%5B1%5D%5BphoneId%5D=106&phones%5B1%5D%5BphoneNumber%5D=787-795-4095&phones%5B1%5D%5BphoneType%5D=FAX
Nimchip
  • 1,685
  • 7
  • 25
  • 50

4 Answers4

3

I see a few things that don't look right

unless you have getters and setters in your wrappers (DTO is a better name), i don't use them for my DTOs for xhr calls, you need to change

public class PhoneWrapper(){
    private String phoneId;
    private String phoneNumber;
    private String phoneType;
}

to have public fields vs private

public class PhoneWrapper(){
    public String phoneId;
    public String phoneNumber;
    public String phoneType;
}

Your js arrays are not arrays but objects;

var phones = {phone1, phone2, phone3};
var schedules = {schedule1, schedule2};

Here they are as arrays

var phones = [phone1, phone2, phone3];
var schedules = [schedule1, schedule2];

Make sure you naming is the same of both the js and java sides. I find it very helpful to turn on the debugging when troubleshooting these problems. log4j -

<logger name="org.springframework.web.servlet.mvc" >
    <level value="debug" />
</logger>

EDIT

So after the question was updated with more info I notice that it was the same problem as Binding a list in @RequestParam

Community
  • 1
  • 1
denov
  • 11,180
  • 2
  • 27
  • 43
  • Unfortunately it was a typo in my original question. They are correctly defined as Arrays (as I am using push to insert the objects into them). I also have sets and gets in my DTO/Wrappers. I went with a different approach, but this question remains open as is. That being said, I upvoted both of you for having a keen eye. Unfortunately it does not resolve my problem :( – Nimchip Aug 06 '13 at 22:02
  • ah! the URL you posted helped. see http://stackoverflow.com/questions/4596351/binding-a-list-in-requestparam – denov Aug 06 '13 at 22:30
  • @Nimchip you should update your question to match your comment above. You still have an object vs an array in the question. – denov Aug 07 '13 at 05:05
  • Ok it's edited. I believe I tried that approach as well but it did not work for some reason. The mapping from Javascript `Object[]` into `PhoneWrapper[]` or `ScheduleWrapper[]` was not being done. It is however, very important that parameters are named with brackets when passing data to the controller, otherwise it will not see it as an incoming array. – Nimchip Aug 08 '13 at 19:14
  • no brackets. your request params should look like myparam=myValue1&myparam=myValue2&myparam=myValue3 – denov Aug 08 '13 at 19:16
  • I seem to recall I tried it this way too, without the brackets in the parameter names, with the brackets in the parameter names and using direct arrays instead of collections, but no go. I just tried it again and nothing. – Nimchip Aug 08 '13 at 20:42
  • did you change your method signature from Set to ScheduleWrapper[]? you may want to switch to http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method-args – denov Aug 08 '13 at 23:02
2

I would say that you are almost there! The first thing the you need is a wrapper to hold the two Set<> parameters since spring is not able to map a collection directly to parameters (yet?).

Also, there are two ways to handle this kind of requests:

  • use a json request and @Requestbody with a single javascript object in the request body an map this into a java class (automatically by spring). This means you need to change a little how the data is send down and this approach has one side effect: you cannot merge data simply by defining the parameter as a model attribute.

  • a second possibility is to stay with the post form submit. Also here you need to create the wrapper and use this one as a requestparam. Either one per Set<> parameter like @Sotirios mentioned in his answer or one parameter which holds both sets. Then you need to modify your submit data to send the phone and schedule information like input fields. I haven't used sets in this case but lists and the parameter names would look like phoneWrapper[0].phoneId. The advantage of the second approach is that you can merge the request data with existing values so you do not need to send down a complete phone information all the time.

Gray
  • 115,027
  • 24
  • 293
  • 354
Martin Frey
  • 10,025
  • 4
  • 25
  • 30
  • updated my question a bit, i'm using getjson and @requestbody now, but curently getting a 400. – Nimchip Jul 26 '13 at 15:17
  • Can you post the exact json string that is send to the server and the updated requestmapping method? Keep in mind that you need exactly one command object which holds two fields Set and Set – Martin Frey Jul 26 '13 at 16:53
  • Yes. I hope you're seeing that I modified all the code in the main post. I posted the getJSON call there with the data object which contains an index, a phones array and a schedules array. I will update the main post with the string. Also the controller method was updated in the post too. – Nimchip Jul 26 '13 at 18:16
  • So you are using form parameters correct? Hard to decode the get url ;) So you cannot map requestparam directly to a set<>! You have to wrap your sets in an object. Build a class MyRequest which contains List schedules and List phones. Use a list just to be sure first. – Martin Frey Jul 28 '13 at 18:55
  • 1
    And else download the spring mvc showcase application and take a look. There are a lot of usecases implemented in a basic manner. https://github.com/SpringSource/spring-mvc-showcase – Martin Frey Jul 28 '13 at 18:56
  • I'll try this then, and report back tomorrow – Nimchip Jul 28 '13 at 19:20
  • Also just re-read your msg, I'm not specifically using a form to send. I'm using jquery's `getPOST` to send an object data which contains the big 3 (the index, the phones array and the schedules array). Of course it does send it as url parameters so it is similar in some ways. – Nimchip Jul 30 '13 at 14:18
  • So i tried what you said and made a new class called `DataWrapper` which contains `List` and `List`. I also tried inserting index in there as well. None worked. I'm convinced there must be something else wrong. Perhaps javascript objects inside javascript objects being too complex for Spring to figure out. I added a bounty to see if I can get more visibility, but if you have any more ideas let me know. – Nimchip Jul 30 '13 at 16:48
1
var phones = {phone1, phone2, phone3};
var schedules = {schedule1, schedule2};

These two are not arrays (square brackets), but objects (curly brackets). Compare with

var phones = ["phone1", "phone2", "phone3"];
var schedules = ["schedule1", "schedule2"];

and if you are to pass actual object references (phone1, phone2, phone3, schedule1 and schedule2 are object variables) then you need to use

var phones = [phone1, phone2, phone3];
var schedules = [schedule1, schedule2];
Zlatin Zlatev
  • 3,034
  • 1
  • 24
  • 32
0

For spring the map request parameters to Class instance fields, they have to match the name of the parameter.

So with

<input type="hidden" name="someParameter" value="123"/>

and

public class SomeClass {
    private String someParameter;
    // getters and setters
}

a Spring controller will be able to be injected with a SomeClass instance whose field someParameter has the value 123 that comes from the html hidden input request parameter. This is also known as a command object.

A javascript array has no meaning to either html or http.

As for the solution, I would keep your class PhoneWrapper, use javascript to populate 3 <input> elements, and change the method definition to

@RequestMapping(value=MY_URL, method=RequestMethod.POST)
public String doSomething(@RequestParam("index") int index, 
                      PhoneWrappers phoneWrappers, 
                      ScheduleWrappers scheduleWrappers,
                      Model model,
                      HttpSession session){

Notice there are no more array [] brackets. (You would do the same for ScheduleWrappers).

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • That's not the solution I'm looking for. I re-edited the question adding the `array` part. I also cannot just map to a single PhoneWrapper, as I am expecting a collection of them. – Nimchip Jul 24 '13 at 19:20
  • I don't believe you can achieve that with pure html and default Spring configuration. If you're expecting a collection of them, you'll need to use a serialization schema like JSON or XML, thus requiring ajax. – Sotirios Delimanolis Jul 24 '13 at 19:28
  • You would have to use a Filter or Interceptor, take the JSON value of the input parameter. Use the json to deserialize into a collection. You have to do this manually. – Sotirios Delimanolis Jul 24 '13 at 19:32
  • Hmm, thank you. I guess I'll wait a bit just in case someone else answers but I suspect you're right. – Nimchip Jul 24 '13 at 19:35
  • perhaps you can help me out, but I edited my title and code to fit ajax. I keep getting a 404. – Nimchip Jul 24 '13 at 21:10
  • @Nimchip If you don't have it, download Firebug to see what URL your ajax call is actually sending the request to. Depending on the page you're on, you might need to put the whole url yourself (you can build it with `jstl`). – Sotirios Delimanolis Jul 24 '13 at 21:17
  • yea i've been using chrome's console... it's being mapped correctly to `localhost:8080/myController/myUrl?index=9&schedules%5B0%5D%5B%scheduleId%5D=1`.... and so on. As far as I know the objects are being constructed fine, but it's not reaching the controller. – Nimchip Jul 24 '13 at 21:43
  • actually you were right, i wasn't passing the right url. Now i'm getting a 400 though (bad request) and no stack trace – Nimchip Jul 24 '13 at 22:46
  • @Nimchip That means that although Spring has a method to call for your request, it can't build the arguments (your command objects) it needs to pass to it. – Sotirios Delimanolis Jul 25 '13 at 00:43