How to return a list as JSON using Spring MVC (@Restcontroller)?

Asked

Viewed 6,465 times

4

I want to return a list in JSON format using Spring, but I am not succeeding. At pom.xml I added this dependency:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>

And mine controller is like this:

@RestController
public class CandidatosWS {

    @Autowired
    CandidatoService candidatoService;

    @RequestMapping(value = "/candidatos/", method = RequestMethod.GET)
    public ResponseEntity<List<Candidato>> allCandidatos() {
        List<Candidato> listCandidatos = null;

        try {
            listCandidatos = candidatoService.listarCandidatos();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return new ResponseEntity<List<Candidato>>(listCandidatos, HttpStatus.OK);
    }
}

However when calling the url by Postman, I get the error:

<html>
    <head>
        <title>Error</title>
    </head>
    <body>Not Acceptable</body>
</html>

There’s one more detail I didn’t do?

2 answers

4

Its loader is not in trouble, apparently the problem is in the dependency of Jackson, which is the standard JSON converter registered by Spring. By default Spring MVC registers these converters:

How Jackson’s Standard Converter Needs a Version 2.*, You must upgrade to a newer version of Jackson, which the registered standard converter makes use of. To this end, amend the dependency statement jackson-mapper-asl in his pom.xml by one of the version 2, something like that:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.6.1</version>
</dependency>

Realize that both the groupId as to the artifactId are different from the version you are using.

There are other ways to do this, but more laborious if it is mandatory to use an older version of Jackson in your project. One is to configure a view resolve that resolves by content (ContentNegotiatingViewResolver) and another is to register a converter that using the mapper of Jackson’s version 1.*. I don’t know how your entire application is, maybe this is not the best alternative, but if this is your case, let me know that I update the response with configuration for these scenarios.

Considerations about serialization in Spring MVC

Suggested this scenario according to what you said in your question, some other points can be checked in your application in search of the solution of your problem.

Something to note is that it is not mandatory to explicitly inform on @RequestMapping that the end-point will produce JSON, or XML, or HTML, as suggested in the other response, this somewhat limits the flexibility that Spring MVC gives you, as in cases where you need to change the type of media returned. Reviewing all mapping is not a very cool service...

Good practice is to set up several messages converters and leave it up to the customer to choose what type of media he wants, ie it is the customer who will tell what type of return he wants through a header. If it is something that our application does not return, then we will return an error or we can still consider one by default.

As stated above Spring already configures several converters, so what we need to do is just ensure the types of media that our application will handle, as well as configure a standard. In XML we can do this way:

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="defaultContentType" value="application/json" />
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

And the equivalent in Java is this:

@Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
    configurer.parameterName("mediaType")
            .defaultContentType(MediaType.APPLICATION_JSON)
            .mediaType("xml", MediaType.APPLICATION_XML)
            .mediaType("json", MediaType.APPLICATION_JSON);
}

In both XML and Java we did the same thing, we said that the default content type will be JSON (defaultContentType) and limit the types of media our application will work with (mediaTypes).

Once this is done, we can test. In Postman a request can be made to http://localhost:8080/<seu_context>/test/candidatos, with or without the header Accept, but we can also use something like curl. This:

curl http://localhost:8080/<seu_context>/test/candidatos

Or this:

curl --header "Accept:application/json" http://localhost:8080/<seu_context>/test/candidatos

You will return this in my example:

[
  {
    "id": 1,
    "nome": "Pedro da Silva"
  },
  {
    "id": 2,
    "nome": "Paula Pereira"
  },
  {
    "id": 3,
    "nome": "<anonimo>"
  }
]

If we inform as value application/xml for the header Accept, we will have this:

<List xmlns="">
    <item>
        <id>1</id>
        <nome>Pedro da Silva</nome>
    </item>
    <item>
        <id>2</id>
        <nome>Paula Pereira</nome>
    </item>
    <item>
        <id>3</id>
        <nome>&lt;anonimo&gt;</nome>
    </item>
</List>

Using curl, it would be something like that:

curl --header "Accept:application/xml" http://localhost:8080/<seu_context>/test/candidatos

Note: on return in XML the name of the property, namespace, etc., can be changed by you.

Finally, some remarks, other good practices:

  • it is not necessary to return a Responseentity, it is usually used when we inform a status HTTP different than the container would return, when we are using RestTemplate or when we do not explicitly inform @ResponseBody. In the latter case, @ResponseBody is already inherited when we use @RestController;
  • it is not necessary to convert anything to a JSONObject, as suggests also the other response, this is work and unnecessary. When we speak through the @ResponseBody that the return will be serialized, which will be the payload response, Spring will choose the most appropriate converter according to the header provided by the client (HTTP header Accept) or set as default. That said, you can simply return a list of candidates (List<Candidato>);

So considering also these remarks, your CandidatosWS might look like this:

@RestController
@RequestMapping(value = "/test")
public class CandidatosWS {

    @Autowired
    private CandidatoService candidatoService;

    @RequestMapping(value = "/candidatos", method = RequestMethod.GET)
    public List<Candidato> allCandidatos() {
        return candidatoService.listarCandidatos();
    }

}

A full working example can be seen in this gist, if you want to see how you can make your controllers simpler, leaving it to Spring to serialize.

0

Altere of:

@RequestMapping(value = "/candidatos/", method = RequestMethod.GET)

To

@RequestMapping(value = "/candidatos/", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)

Then you need to return a list of Jsonobject:

List<JSONObject> listCandidatos = new ArrayList<JSONObject>();

And to populate it, you need to convert the Candidato for a JSONObject. There are different ways to do this:

  • use a library as Gson or the very Jackon for this.
  • convert manually by creating an instance of Jsonobject and filling field by field

And to return:

return new ResponseEntity<List<JSONObject>>(listCandidatos, HttpStatus.OK);

Final code

The code would look like this, so:

@RequestMapping(value = "/candidatos/", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Candidato>> allCandidatos() {

    List<Candidato> listCandidatos = /* busca de candidatos*/

    List<JSONObject> listCandidatosJson = new ArrayList<JSONObject>();
    for (Candidato cand : listCandidatos ) {
        JSONObject candidatoJson = cand.toJson(); //implemente o método toJson(), conforme sugerido
        listCandidatosJson.add(candidatoJson);
    }

    return new ResponseEntity<List<JSONObject>>(listCandidatosJson, HttpStatus.OK);
    }
}
  • No need to convert anything to JSONObject, spring already has converters for that, just set up. Ever imagined having to implement toJson(); in all entities, or even call explicitly in the controller to be serialized? It’s a lot of work that spring already does for us ;)

  • @Brunocésar, thank you for your comments. It was just an example the code above, it would not need to implement in all entities, depends on the need for it. I didn’t know Spring had converters, I always saw code using Jackson and Gson explicitly converting objects to JSON. Anyway, I see no reason to deny an answer that, as far as I know, is not incorrect.

  • @Dherik I think the only problem with the answer is the word accurate. The user does not need to mount JSON manually. On the other hand, it is an excellent alternative, since an object does not always give us the desired mapping or even we want to create a class to represent a simple value. In addition to that the performance of assembling a JSON manually is far superior to that which uses reflection. So it is always good to keep in mind an alternative.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.