2

I need to submit a JS-formed Array of Objects to a Spring MVC Controller. All the property names match.

@PostMapping("/addAdmin")
public void addAdmin(@RequestParam List<UserRolesGUIBean> userRolesGUIBeans)
{
    // ...      
}   

JS:

 var entries = [];
 //...
 // entries is an array of objects of the form {id: "..", role: ".."}
 // verified to be correct before submission

 $.ajax({
        type : "post",
        dataType : "json", 
        url : 'addAdmin',  
        data : JSON.stringify(entries)
 })

Bean

public class UserRolesGUIBean implements Serializable {
    private String id;
    private String role;
    // + Constructors (Empty + Full), Getters and setters
}

Error:

Required List parameter 'userRolesGUIBeans' is not present]

Also tried this with ModelAttribute and an ArrayList,

PostMapping("/addAdmin")
    public void addAdmin(@ModelAttribute ArrayList<UserRolesGUIBean> userRolesGUIBeans)  {

Now there are no errors, but the list is empty, no data was received.

Tried everything -- arrays vs. lists, JSON.stringify(data) or a data object with data {"entries" : entries}, RequestBody doesn't work and gives UTF Errors; and RequestParam as above doesn't work either.

This is way too complicated for a simple task.

halfer
  • 19,824
  • 17
  • 99
  • 186
gene b.
  • 10,512
  • 21
  • 115
  • 227

3 Answers3

2

You are trying to send a JSON object by using a post. You should use @RequestBody annotation.

Try to change your method in this way:

@PostMapping("/addAdmin")
public void addAdmin(@RequestBody List<UserRolesGUIBean> userRolesGUIBeans)
{
    // ...      
} 

In this way Spring will intercept the Json and transform it in List of wished objects

Angelo Immediata
  • 6,635
  • 4
  • 33
  • 65
  • Nope, unfortunately doesn't work. The error with this is `org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported`. It was reported here as well, in the comments to the suggestion, https://stackoverflow.com/a/44696610/1005607 . In fact the solution to this UTF Error is to **remove** `RequestBody`, https://stackoverflow.com/a/38252958/1005607 – gene b. May 09 '19 at 16:55
  • You must set content type as application/json – Angelo Immediata May 09 '19 at 17:13
  • @geneb. I think you should go back to the basic and understand the purpose of various annotations & default behaviours of Spring. The code Angelo gave you is what I've written in so many endpoints. Use Postman to submit a request to your endpoint with `Content-Type` set to `application/json` first. Make it work there and then go back to your JS code later. – Mr.J4mes May 09 '19 at 17:14
  • @AngeloImmediata same UTF error even with `application/json`, I tried it. – gene b. May 09 '19 at 17:15
  • I think the problem is, I'm **not** doing Form Submission (`@ModelAttribute`), so I have to submit a "custom" serialized JSON string as `@RequestBody`.But when I do that custom `@RequestBody`, I can receive a plain String in the Controller, but I can't receive a mapped object like with `@ModelAttribute`. The following works as a string: `@RequestBody String json` and with the regular `json` datatype in JS. – gene b. May 09 '19 at 17:26
2

SOLUTION:

1) In theory, if I was doing Form Submission (like $('#myForm').submit()), I could use @ModelAttribute to automatically bind my form to the bean. That's what @ModelAttribute does -- it's used for Form Submission. I don't have a real form; only my own custom values.

I could still "fake" a Form Submit by creating a Dynamic Form "on the fly," but I couldn't get the Arrayed-Field Form Submission (e.. obj[] with [] notation in the HTML Name) to map to a @ModelAttribute List<Bean>, so I disregarded this unusual Form Submit approach.

2) The real approach that worked is to just submit a custom JSON string which is my own. Not related to any form submission, so can't use @ModelAttribute. Instead, this is the @RequestBody approach. Then I have to parse the JSON RequestBody String myself -- and here we have to use Jackson, Java JSON, or GSON to parse the JSON Array.

In my case,

JS:

 $.ajax({
        type : "post",
        dataType : 'json', 
        url : 'addAdmin',  
        data : JSON.stringify(entries)
 })

Controller (note it takes a custom String only). Then it uses Jackson to parse the string manually, because Spring won't do it in this case. (Spring will only auto-parse if you're using @ModelAttribute form binding.)

@PostMapping("/addAdmin")
public boolean addAdmin(@RequestBody String json) throws Exception {

      String decodedJson = java.net.URLDecoder.decode(json, "UTF-8");
      ObjectMapper jacksonObjectMapper = new ObjectMapper(); // This is Jackson
      List<UserRolesGUIBean> userRolesGUIBeans =  jacksonObjectMapper.readValue(
              decodedJson, new TypeReference<List<UserRolesGUIBean>>(){});
      // Now I have my list of beans populated.

}

gene b.
  • 10,512
  • 21
  • 115
  • 227
  • The whole point of using Spring Boot and Spring MVC is to let the framework do this kind of boiler plate work for you. There're losts of tutorials on the Internet showing you how to achieve it like this one (https://www.javadevjournal.com/spring/spring-request-response-body/). If you think this is the 'real' approach to work with Spring, you certainly have a ton of manual work waiting for you ahead. – Mr.J4mes May 10 '19 at 02:52
  • Those are the easy cases -- of course I can do `@RequestBody MyBean bean`. As soon as you deviate from the happy path, like with Arrays/Lists, it's not so simple anymore. There's no way to map a `@RequestBody List` or an array automatically. – gene b. May 10 '19 at 03:36
  • 1
    Bro, you keep believing your scenario is special for some reason. Don't you think the people behind the framework has already worked with your situation before? I will give you a minimal runnable code to prove it works even with List. – Mr.J4mes May 10 '19 at 03:53
  • 1
    I agree with @Mr.J4mes You are missing something in your scenario – Angelo Immediata May 10 '19 at 08:16
2

As promised, please go to https://start.spring.io/ and create a new project with a single depdendency for spring-boot-starter-web.

After that, you can create the following bean in your project.

import java.util.List;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
    @PostMapping("/request-body")
    public void getRequestBody(@RequestBody List<Person> list) {
        for (Person person : list) {
            System.out.println(person.name);
        }
    }

    public static class Person {
        private String name;
        private String phoneNo;

        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
        /**
         * @param name the name to set
         */
        public void setName(String name) {
            this.name = name;
        }
        /**
         * @return the phoneNo
         */
        public String getPhoneNo() {
            return phoneNo;
        }
        /**
         * @param phoneNo the phoneNo to set
         */
        public void setPhoneNo(String phoneNo) {
            this.phoneNo = phoneNo;
        }
    }
}

Nothing special, just take in a list of Person and print out the names. You can right click and run the project directly from the IDE.

You can open Postman and make a POST request as following.

enter image description here enter image description here

This is what gets printed in the console.

enter image description here

If it works with Postman, you can make it work in JS. You just haven't figured out how. Instead of settling with that "workaround" you found, I think you should find out the proper way to submit a request in JS. In addition, some understanding of the Spring framework would help too. Otherwise, you will just keep randomly trying stuff like @ModelAttribute without getting anywhere.

Mr.J4mes
  • 9,168
  • 9
  • 48
  • 90