REST GET list as JSON - java

I have a list of ids which I need to pass as an argument to be displayed in JSON format. Specifically, i put the values in a List and would like my GET method to return them as:
[ 1, 5, 12,...]
I tried using GenericList, but I get a MessageBodyWriter error:
MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.List<java.lang.Long>.
I'm using Jersey 2.16, and I've had no problems outputting lists of custom classes. The code is here:
#GET
#Path("{name}")
#Produces(MediaType.APPLICATION_JSON)
public Response getUsersByName(#PathParam("name") String name){
List<Long> userIds = userService.getUserssByName(name);
GenericEntity<List<Long>> list = new GenericEntity<List<Long>>(userIds){};
return Response.ok(list).build();
}
UserService queries a hashmap for all the users whose name matches a certain name and returns a list of their keys:
public List<Long> getUserssByName(String name){
List<Long> ids = new ArrayList<>();
for (Entry<Long, User> entry : users.entrySet()){
User user = entry.getValue();
if (user.getName().equals(name)){
ids.add(entry.getKey());
}
}
return ids;
}
How can I get that list to be displayed as I've stated above?

I think this explanation-answer will serve better than just "fixing" your code
By default, jersey provides a set of entity providers that can be used for given media types. For application/json, the following types are provided by default:
byte[]
String
InputStream
Reader
File
DataSource
StreamingOutput
To add support for serialization of other types to json is a bit of a rocky slope with the tools you are currently working with. I'll explain...
Jersey is just a framework for developing RESTful web services in java. There was a time when REST didn't exist (or wasn't seen as useful) and before that concept came about there were already many concrete implementations to facilitate Http data transfer. JAX-RS was one of them. This was during a time when javascript was likely in its infancy and XML largely ruled the world wide web. JAX-RS wasn't designed to natively prevent type erasure, and type erasure is precisely what we are trying to achieve. For me to go through the extremely long-winded solution of implementing the behaviors in MessageBodyWriter would be reinventing the wheel, and no one likes to reinvent the wheel! But, if you are interested in writing your own entities, or you are interested in learning what entities are and how to customize them, head over to https://jersey.java.net/documentation/latest/message-body-workers.html. Reading this document will outline what exactly is going on with your issues.
Solution
Choose a service. Learn it, and do not reinvent the wheel. There are many services out there that can achieve this. My favorite tool is Gson. It is a Java library developed by Google to convert Java objects into their JSON representation and vice versa.
Want to know how simple this is to fix your code?
#GET
#Path("{name}")
#Produces(MediaType.APPLICATION_JSON)
public Response getUsersByName(#PathParam("name") String name){
List<Long> userIds = userService.getUserssByName(namee);
Type listType = new TypeToken<List<Long>>() {}.getType();
Gson gson = new Gson();
String userIdList = gson.toJson(userIds, listType);
return Response.ok(userIdList).build();
}
Now, jersey supports application/json for String entity responses. Pretty much all of your responses will be represented as a String, type erasure is prevented, and you become a more happy developer.
Hope this helps!

You have to convert the list to array.
#GET
#Path("{name}")
#Produces(MediaType.APPLICATION_JSON)
public Response getUsersByName(#PathParam("name") String name){
List<Long> userIds = userService.getUserssByName(namee);
Long[] userIdArray = userIds.toArray();
return Response.ok(userIdArray).build();
}

Related

WebClient does not return a "valid" list of Strings

I have a spring boot app that among others, has an endpoint that when hit, returns a list of strings. I also have another spring boot app that hits the first app's endpoint to get the data. The fetch code:
return webClient.get().uri("/sensors/get-cities").headers(httpHeaders -> {
httpHeaders.set("Authorization", auth);
}).retrieve()
.bodyToFlux(String.class).collectList().block();
The above yields a list but with this format when I inspect it in the debbuger, "["city"]". The outer double quotes, I get them because it's a string but the brackets and the inside double quotes, I do not. I tried replacing these characters but I had no luck with the brackets (tried regex). It is like they are not there, but at the same time they are. I am confused at this point. But I think that the behavior of the fetch code is not normal, it should yield a valid array of strings.
What you are probably getting (im guessing here) is a response body that looks something like this:
[
"New York",
"Madrid",
"London"
]
You then tell webflux that you want to convert the body to a Flux of String by calling bodyToFlux(String.class).
So the framework takes the entire response and makes a string out of it
// A string of the entire array (im escaping the quotation marks)
"[\"New York\",\"Madrid\",\"London\"]"
And then the framework will throw the entire thing into a Flux which means it takes the first position in the Flux. You then emit all the values into a List by calling collectList The equivalent code is sort of:
List<String> oneString = Flux.just("[\"New York\",\"Madrid\",\"London\"]")
.collectList()
.block();
So you get a list, with one string in it, which is the entire body.
What you probably want to do is to get a list out if it. And this is one way to do it:
List<String> strings = webClient.get()
.uri("/sensors/get-cities")
.headers(httpHeaders -> {
httpHeaders.set("Authorization", auth);
})
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<String>>() {})
.block();
Spring explains ParameterizedTypeReference:
The purpose of this class is to enable capturing and passing a generic Type. In order to capture the generic type and retain it at runtime
So its sort of a class that makes sure we can use generic types like List<T> and helps us with type information.
So what we do is that we now take the response and tell the framework that the body is a list of strings directly. We dont need to do collectList anymore as the framework will stick it in a list for us. We then call block to wait in the response.
Your Springboot API returns result as parsed to JSON (this is default behavior). So it first builds a list of Strings (in your case just a single String "city" and than serializes it to Json. In this case since it is a list it serializes it to JSON array as opposed to JSON Object. Read about JSON here. So in your second Springboot app that hits the API from the first one should assume that you are getting JSON which you need to parse to get your list. To parse it you can use readValue() method of ObjectMapper class of Json Jackson library which is a default JSON library in Springboot. your code would be
List<String> myList;
ObjectMapper = new ObjectMapper();
//Add setters for ObjectMapper configuration here if you want a specific config
try {
myList = objectMapper.readValue(myJsonString, List.class);
} catch(IOException ioe) {
...
}
In addition I wrote my own Open-source library called MgntUtils, that includes JsonUtils class which is a thin wrapper over Json Jackson library. It provides just Json parser and serializer, but in many cases that is all you need. With my library you would only need one dependency as oppose to Jackson, and JsonUtils class just have 4 methods, so by far easier to understand. But in your case if you use my library the code would be very similar to the above code. It would be something like this:
List<String> myList;
try {
myList = JsonUtils.readObjectFromJsonString(myJsonString, List.class);
} catch(IOException ioe) {
...
}
Note that in this case you won't have to instantiate and configure ObjectMapper instance as readObjectFromJsonString is a static method. Anyway if you are interested in using my library you can find maven artifacts here and The library itself with source code and javadoc is on Github here. Javadoc for JsonUtils class is here

How to consume a Spring HAL/HATEOAS API in Java using purely Jackson, not Spring

We are trying to create a Java client for an API created with Spring Data.
Some endpoints return hal+json responses containing _embedded and _links attributes.
Our main problem at the moment is trying to wrap our heads around the following structure:
{
"_embedded": {
"plans": [
{
...
}
]
},
...
}
When you hit the plans endpoint you get a paginated response the content of which is within the _embedded object. So the logic is that you call plans and you get back a response containing an _embedded object that contains a plans attribute that holds an array of plan objects.
The content of the _embedded object can vary as well, and trying a solution using generics, like the example following, ended up returning us a List of LinkedHashMap Objects instead of the expected type.
class PaginatedResponse<T> {
#JsonProperty("_embedded")
Embedded<T> embedded;
....
}
class Embedded<T> {
#JsonAlias({"plans", "projects"})
List<T> content; // This instead of type T ends up deserialising as a List of LinkedHashMap objects
....
}
I am not sure if the above issue is relevant to this Jackson bug report dating from 2015.
The only solution we have so far is to either create a paginated response for each type of content, with explicitly defined types, or to include a List<type_here> for each type of object we expect to receive and make sure that we only read from the populated list and not the null ones.
So our main question to this quite spread out issue is, how one is supposed to navigate such an API without the use of Spring?
We do not consider using Spring in any form as an acceptable solution. At the same time, and I may be quite wrong here, but it looks like in the java world Spring is the only framework actively supporting/promoting HAL/HATEOAS?
I'm sorry if there are wrongly expressed concepts, assumptions and terminology in this question but we are trying to wrap our heads around the philosophy of such an implementation and how to deal with it from a Java point of view.
You can try consuming HATEOS API using super type tokens. A kind of generic way to handle all kind of hateos response.
For example
Below generic class to handle response
public class Resource<T> {
protected Resource() {
this.content = null;
}
public Resource(T content, Link... links) {
this(content, Arrays.asList(links));
}
}
Below code to read the response for various objects
ObjectMapper objectMapper = new ObjectMapper();
Resource<ObjectA> objectA = objectMapper.readValue(response, new TypeReference<Resource<ObjectA>>() {});
Resource<ObjectB> objectB = objectMapper.readValue(response, new TypeReference<Resource<ObjectB>>() {});
You can refer below
http://www.java-allandsundry.com/2012/12/json-deserialization-with-jackson-and.html
http://www.java-allandsundry.com/2014/01/consuming-spring-hateoas-rest-service.html

Duplicate Entries in Swagger

I'm writing an API using the HalBuilder library for HAL representations.
As it stands now, I need to have two different methods for the JSON and HAL representations. As an example, my VersionResource includes the following two methods:
#GET
#ApiOperation(value = "Find all versions", response = Version.class, responseContainer = "List")
#Produces({MediaType.APPLICATION_JSON})
public Response getAsJson() {
List<Version> versions = repository.selectAll();
return Response.ok().entity(versions).build();
}
#GET
#ApiOperation(value = "Find all versions", notes="Returns HAL format", response = Representation.class, responseContainer = "List")
#Produces({RepresentationFactory.HAL_JSON})
public Representation getAsHalJson() {
List<Version> versions = repository.selectAll();
return this.versionRepresentationFactory.createResourceRepresentation(versions);
}
(Note: I'm sure there's a better way of collapsing these methods, and I'm looking into a way to do that)
But my immediate problem is that using two methods causes duplicate entries in my Swagger documentation:
Those two GET /versions are effectively the same thing, but they have different return types, so Swagger wants them to be different.
I'd like to collapse those two. What are my options here?
[It's probably worth pointing out that I'm using the Swagger Maven plugin to generate my documentation. The application is also using Guice for DI and Jersey for JSON representations.]
I read in https://github.com/swagger-api/swagger-spec/issues/146#issuecomment-59082475:
per design, we don't overload response type definitions for the same response code.
So I think the Maven plugin creates an invalid Swagger document.
What are your options?
Be patient and watch these Swagger issues: https://github.com/swagger-api/swagger-spec/issues/146 and https://github.com/swagger-api/swagger-spec/issues/182
Don't use Swagger

dojo grid sorting and RESTEasy JAX-RS

Dojo grids implement sorting by by issuing requests to REST webservices like so:
GET http://server/request?sort(+id)
Where sort(+id) is the direction (+/-) and the column to sort on.
Currently I'm doing this, and it works, but its ugly:
#GET
#Path("request/")
#Produces(MediaType.APPLICATION_JSON)
public Collection<RequestDataWithCurrentStatus> getAllRequests() {
Collection<RequestDataWithCurrentStatus> col = new ArrayList<RequestDataWithCurrentStatus>();
....
//handle the various types of sorting that can be requested by the dojo widget
String queryString = this.request.getQueryString();
//parse the dojo sort string 'sort(+id)' and sort
...
return col;
}
This method uses a RESTEasy injected #Context private HttpServletRequest request to get access to the raw query string.
I feel like I should be able to map this query string to one of RESTEasy's #*Param annotations in my getAllRequests() invocation. But according to the documentation for RESTEasy, there doesn't seem to be a good mapping to the screwy dojo query string from the doc. I want to do something like this:
#GET
#Path("request/")
#Produces(MediaType.APPLICATION_JSON)
public Collection<RequestDataWithCurrentStatus> getAllRequests( #DojoQueryParam String sort) {
...
}
How do I marshal dojo grid query strings to RESTEasy webservice methods the right way?
I have scarce experience with the old stores / dojox grids, but in case you are using dojo/store/JsonRest: You can change the way it sends the sort param with sortParam. Shamelessly snagged from the reference guide:
var store = new JsonRest({
target: "/FooObject/",
sortParam: "sortBy"
});
This should make requests on the form /foo/bar?sortBy=+id.
http://dojotoolkit.org/reference-guide/1.8/dojo/store/JsonRest.html#sorting

Beyond the #Produces annotation, how does Jersey (JAX-RS) know to treat a POJO as a specific mime type?

I see a lot of examples for Jersey that look something like this:
public class ItemResource {
#GET
#Path("/items")
#Produces({"text/xml", "application/json"})
public List<Item> getItems() {
List<Item> items = new ArrayList<Item>();
Item item = new Item();
item.setItemName("My Item Name!");
items.add(item);
return items;
}
}
But then I have trouble dissecting Item, and how Jersey knows how to translate an Item to either XML or JSON. I've seen very basic examples that just return a String of constructed HTML or XML, which makes more sense to me, but I'm missing the next step. I looked at the samples, and one of them stood out (the json-from-jaxb sample), since the object was marked with these types of annotations:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"flight"
})
#XmlRootElement(name = "flights")
I'm looking for tutorials that cover this "translation" step-by-step, or an explanation here of how to translate a POJO to output as a specific mime type. Thanks!
There are two things at work here. First, the media types in the #Produces annotation are used in content negotiation. The media types in the value of the Accept header sent by the client are compared to those in the #Produces annotation and the most appropriate one is selected. Suppose that is text/xml in your example.
When constructing the response body Jersey internally tries to find a MessageBodyWriter that can turn Item objects into text/xml. Usually the programmer supplies these 'mapper' classes but for XML and JSON Jersey has built in MessageBodyReaders already for convenience.
That is why it appears as if there was going on some magic.
Jan

Categories