Jersey is unable to encode entity as JSON - java

I got several classes that are annotated with meta information:
#Metadata(id="Foo",number=3)
public class Foo {
...
}
#Metadata(id="Bar",number=11)
public class SomeBar {
...
}
Now I want to publish this meta information via Jersey, returning it as a JSON structure. To do so I created a resource:
...
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response list(){
...
List<Object> result=new LinkedList<>();
for each class in (classes annotated with #Metadata) { //pseudo
Metadata md=annotatedclass.getAnnotation(Metadata.class);
result.add(md);
}
return Response.ok(result).build();
}
A browser call to this method fails with HTTP 500 / Server Error - without any further info! No exceptions in debug log, no extended info on the error page.
What actually works is translating Metadata objects "by hand". Substituting result.add(md) with ObjectMapper mapper=new ObjectMapper(); result.add(mapper.writeValueAsString(md)); works perfectly. I get all the metadata objects serialized as (JSON-)Strings.
So what is Jetty doing different in serializing my metadata annotations?

Related

Spring boot #RequestBody default POJO mapping behavior?

I have a java class with uppercase field names and some of them with under scroll, like this:
public class DATADto {
private String UPPERCASE;
private String UNDER_SCROLL;
public String getUPPERCASE() { return UPPERCASE; }
public void setUPPERCASE(String s) { UPPERCASE = s; }
...//setters and getters
}
and I used this in a rest endpoint that accepts json in a spring rest controller:
#RestController
#RequestMapping({"/api/path"})
public class MyRestController {
#PostMapping(path = {"/Data"}, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> useDATADto(#RequestBody DATADto aDATADto ) {
//do something
}
}
what JSON fields do I need to send by default and why?
The story goes like this..
Spring Boot by default uses Jackson ObjectMapper to serialize and deserialize Java objects.
In this context, by serialization we mean the conversion of java objects into json, deserialization is the reverse process.
Regarding the #RequestBody annotation, the following is written in the documentation:
Annotation indicating a method parameter should be bound to the body
of the web request. The body of the request is passed through an
HttpMessageConverter to resolve the method argument depending on the
content type of the request. Optionally, automatic validation can be
applied by annotating the argument with #Valid.
In short, #RequestBody annotation tells Spring to deserialize an incoming request body into an object passed as a parameter to the handler method. Spring achieves this using MessageConverter
Since Spring Boot uses Jackson by default for serializing and deserializing request and response objects in your REST APIs, and Jackson uses MappingJackson2HttpMessageConverter, so that will be message converter implementation that spring will use. You can read more about that here.
The important thing is that Jackson uses Java Bean naming conventions to figure out the json properties in a Java class. Acutally it uses default PropertyNamingStrategy . Here is what is written in documentation:
In absence of a registered custom strategy, default Java property
naming strategy is used, which leaves field names as is, and removes
set/get/is prefix from methods (as well as lower-cases initial
sequence of capitalized characters).
So, since you didn't set any naming strategy, it will use default one.
Beacause of that, if you send payload like this :
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}
That won't work, you will get exception, since there jackson won't find under_scroll property in your class, it will look for under_SCROLL , therefore this payload:
{
"uppercase": "YOUR_VALUE",
"under_SCROLL": "YOUR_VALUE"
}
will work.
To change default PropertyNamingStrategy check
this article.
It will depend on the Jackson property naming strategy. The default is LOWER_CAMEL_CASE , so your request body should look like this:
{
"uppercase": "test",
"under_scroll": "test"
}
For all possible configurations of the naming strategy for Jackson please refer to the document «Class PropertyNamingStrategy»
If you're using Spring, you may use this property to configure the naming strategy:
spring.jackson.property-naming-strategy
Another possible way will be the bean configuration:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder jacksonMapper = new Jackson2ObjectMapperBuilder();
jacksonMapper.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE);
return jacksonMapper;
}
Additional note:
Your current naming approach doesn't follow the Java Code Conventions. If you need to process JSON with some specific naming format better to use the #JsonProperty annotation on the fields of your POJO.
Please see the example below:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
#Data
public class DATADto {
#JsonProperty("UPPERCASE")
private String uppercase;
#JsonProperty("UNDER_SCROLL")
private String underScroll;
}
You should send post request to /api/path/data with this request body:
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}

Lazy loading object causing issue during serialization. Getting 500 internal server error

I have written API's which are resulting in 500 error when hit through postman or browser. However, when I debug and see server is not throwing any error and in fact returning a proper response. Other controller I have implemented in a similar way is returning expected result. below is my controller code. Has anyone faced similar situation. Kindly help.
#CrossOrigin
#GetMapping(value="/byPatientId/{patientId}", produces = "application/json")
public List<ContactInfo> getAllContacts(#PathVariable String patientId) {
logger.info("Received request for List of ContactInfo for patientId: "+patientId);
List<ContactInfo> list =
contactInfoService.getAllContacts(patientId);
return list;
}
#CrossOrigin
#GetMapping("/byContactId/{contactId}")
public ContactInfo getContactById(#PathVariable Integer contactId) {
logger.info("Received request for ContactInfo for contactId: "+contactId);
return contactInfoService.getContactById(contactId);
}
The problem was with one of the dependent object which was having oneToMany relationship with the return type object and it was set to Lazy loading and issue was during the serialization.
Either we can change it to Eager loading or ignore the dependent object by adding #JsonIgnore on dependent object.
I handled it by adding #JsonIgnore annotation on top of the dependent object as I don't need the dependent object in this particular usecase. Issue is solved now.
How is your Controller annotated? is it with #Controller or #Rest?
#RestController = #Controller + #ResponseBody(for serializing the response and pass it into the HttpResponse.
Add the #ResponseBody in your methods on the controller or change the #Controller tag into a #RestController(take into account that #RestController is available since 4.0 Spring version).
More info:https://www.baeldung.com/spring-controller-vs-restcontroller

Configuring Jersey 1.18.x server for JSON POJO mapping

Jersey 1.18.1 here. I have the following Jersey resource defined on my server:
#Path("/location")
#Produces(MediaType.APPLICATION_JSON)
public class LocationResourceImpl implements LocationResource {
private ObjectMapper mapper;
public LocationResourceImpl() {
super();
mapper = new ObjectMapper();
// TODO: Configure for JSON POJO mapping how?!?
}
#GET
#Path("address/{address_id}")
#Override
public Address getAddress(#PathParam("address_id") Long id) {
Address address;
address = new Address(
1L,
"19 ABC Dr",
"Suite 3",
"Testville",
"NY",
"US",
"12345");
return address;
}
}
My Address POJO is properly annotated with #JsonProperty annotations. I am trying to figure out how to configure my ObjectMapper instance so that the Address instance returned by getAddress(Long) returns my address as JSON.
Any ideas as to what I can do? It look like setSerializationConfig and setDeserializationConfig methods were added in Jersey 2.x, but upgrading isn't an option for me, as I'm using DropWizard 0.7.1, which depends on Jersey 1.18.x.
With Dropwizard, we don't need any special configuration for basic Jackson POJO mapping support. As explained in the Dropwizard User Guide: How it's Glued Together:
When your application starts up, it will spin up a Jetty HTTP server, see DefaultServerFactory. This server will have two handlers, one for your application port and the other for your admin port.
The application port has an HttpServlet as well, this is composed of DropwizardResourceConfig, which is an extension of Jersey’s resource configuration that performs scanning to find root resource and provider classes.
DropwizardResourceConfig is where the various ResourceMethodDispatchAdapter are registered to enable the following functionality:
Enables using Jackson to parse request entities into objects and generate response entities from objects, all while performing validation.

Simple Rest to return Json using jersey

Hi I'm trying to return Json from a method in a simple rest using Jersey
HelloWorldService.class
#Path("/hello")
public class HelloWorldService {
#GET
#Path("/empdetail/{id}")
#Produces(MediaType.APPLICATION_JSON)
public EmpDetailsVo getEmpDetails(#PathParam("id") String id){
EmpDetailsVo details = new EmpDetailsVo();
details.empId="1202";
details.empName="Akhi";
details.empAddress="123 Main St. Newark PA 19121";
return details;
}
}
EmpDetailsVo class has getter and setters for empId, name and address.
When I try to run this url:
http://localhost:8080/jerseyRest/rest/hello/empdetail/1202
I get Http status 500.
And on console I see an error:
SEVERE: A message body writer for Java class jerseyRest.EmpDetailsVo, and Java type class jerseyRest.EmpDetailsVo, and MIME media type application/json was not found
And
javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class jerseyRest.EmpDetailsVo, and Java type class jerseyRest.EmpDetailsVo, and MIME media type application/json was not found
Can someone please help.
You need to tell Jersey how to marshal and unmarshal objects from the EmpDetailsVo type into JSON.
Check this tutorial for an example of how this can be done. Here's another example using a different approach. Investigate the use of the com.sun.jersey.api.json.POJOMappingFeature parameter provided to your web application via web.xml, and that should get you there.

REST. Jersey. How to programmatically choose what type to return: JSON or XML?

I have 2 questions:
1. Can I create one class, annotate it with JAXB annotations(for XML support) and declare in web.xml
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
for JSON (Jackson library) support? Or I need to create separately two classes for JSON and XML?
Or may be exist some more elegant way to cause REST service to return both JSON and XML?
2. How I can programmatically choose what type to return (JSON or XML)?
Thanks.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Can I create one class, annotate it with JAXB annotations(for XML support) and declare in web.xml for JSON (Jackson library) support?
You can always use an Application class to specify a MessageBodyReader/MessageBodyWriter for the JSON binding. I believe Jackson provides an implementation in its jar. Below is an example of an Application class that specifies MOXy as the JSON provider:
package org.example;
import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class CustomerApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>(2);
set.add(MOXyJsonProvider.class);
set.add(CustomerService.class);
return set;
}
}
Or I need to create separately two classes for JSON and XML?
EclipseLink JAXB (MOXy) offers native XML binding and is designed to enable you to use the same object model for both JSON and XML. You can integrate it into your JAX-RS application using the MOXyJsonProvider class:
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
How I can programmatically choose what type to return (JSON or XML)?
Server Side
You can specify that your service offers both XML and JSON messages using the #Produces annotation.
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("{id}")
public Customer read(#PathParam("id") long id) {
return entityManager.find(Customer.class, id);
}
For More Information
http://blog.bdoughan.com/2012/03/moxy-as-your-jax-rs-json-provider.html
Client Side
You can use the MediaType to indicate the type of message. Below is an example using Jersey client APIs. Note how the URL is the same, just the requested media type is different.
Client client = Client.create();
WebResource resource = client.resource("http://localhost:8080/CustomerService/rest/customers");
// Get XML response as a Customer
Customer customer = resource.path("1")
.accept(MediaType.APPLICATION_XML)
.get(Customer.class);
System.out.println(customer.getLastName() + ", "+ customer.getFirstName());
// Get JSON response as a Customer
Customer customer = resource.path("1")
.accept(MediaType.APPLICATION_JSON)
.get(Customer.class);
System.out.println(customer.getLastName() + ", "+ customer.getFirstName());
For More Information
http://blog.bdoughan.com/2010/08/creating-restful-web-service-part-55.html
If your client wants to use a part of the URL to configure the response type, you can use a Servlet filter.
An easy way to implement overriding the representation (media type) could use a URL query parameter:
/resources/todo?format=json
The Servlet filter parses the URL query parameters, and if a format=json is present, replaces or adds the accept header "application/json".
No need for seperate classes, what you need is seperate methods:
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Todo getXML() {
Todo todo = new Todo();
todo.setSummary("This is my first todo");
todo.setDescription("This is my first todo");
return todo;
}
Then in the client side, when you request for the service, you indicate in what format you want it:
// Get XML
System.out.println(service.path("rest").path("todo").accept(MediaType.TEXT_XML).get(String.class));
// Get XML for application
System.out.println(service.path("rest").path("todo").accept(MediaType.APPLICATION_XML).get(String.class));
// Get JSON for application
System.out.println(service.path("rest").path("todo").accept(MediaType.APPLICATION_JSON).get(String.class));

Categories

Resources