jackson - jackson not creating json for nested objects - java

I have a user list class and an API that returns user list and total records.
The class is as follows :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class FMSResponseInfo {
#JsonProperty("status")
private String status;
#JsonProperty("message")
private String message;
#JsonProperty("data")
private Object data;
#JsonProperty("status")
public String getStatus() {
return status;
}
#JsonProperty("status")
public void setStatus(String status) {
this.status = status;
}
#JsonProperty("message")
public String getMessage() {
return message;
}
#JsonProperty("message")
public void setMessage(String message) {
this.message = message;
}
#JsonProperty("data")
public Object getData() {
return data;
}
#JsonProperty("data")
public void setData(Object data) {
this.data = data;
}
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class UserListResDTO {
#JsonProperty("users")
private List<UserDTO> users;
#JsonProperty("totalRecords")
private long totalRecords;
public List<UserDTO> getUsers() {
return users;
}
public void setUsers(List<UserDTO> users) {
this.users = users;
}
public long getTotalRecords() {
return totalRecords;
}
public void setTotalRecords(long totalRecords) {
this.totalRecords = totalRecords;
}
}
I am setting an object of type UserListResDTO in FMSResponseInfo as shown below.
I have been successful in creating web services and returning response as json, so far. But the problem I am facing is that the API returns the response as follows :
{"data":"org.package.UserListResDTO#70783307","message":"Success","status":"200"}
And this is how I have written the web service :
#Path("/getUsers")
#GET
#Produces(MediaType.APPLICATION_JSON)
public FMSResponseInfo getUsers(#QueryParam("page") #DefaultValue("0") int page) {
System.out.println("In getUsers()");
FMSResponseInfo fmsResponseInfo = new FMSResponseInfo();
try {
UserListResDTO userList = fmsUserManager.getAllUsers(page);
fmsResponseInfo.setData(userList);
fmsResponseInfo.setStatus(FMSErrorMessageEnum.SUCCESS_CODE.getValue());
fmsResponseInfo.setMessage(FMSErrorMessageEnum.SUCCESS_MESSAGE.getValue());
} catch (Exception e) {
return FMSUtil.getErrorResponseInfo(FMSErrorMessageEnum.INTERNAL_SERVER_ERROR_CODE.getValue(),
e.getMessage());
}
System.out.println("Out getUsers()");
return fmsResponseInfo;
}
I guess there is some problem with the dependencies or something that I am unable to resolve. Major dependencies in my pom are :
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
and I am creating Web services by extending Application class as follow :
#ApplicationPath("rest")
public class FMSApplication extends Application {
public Set<Class<?>> getClasses(){
Set<Class<?>> set = new HashSet<Class<?>>();
set.add(FMSUserManagerWebService.class);
set.add(FMSDocumentManagerWebService.class);
set.add(FMSInboxManagerWebService.class);
set.add(FMSLocationManagerWebService.class);
return set;
}
}
Any help will be really appreciated as I am new to this REST web services and have been stuck for quite long.

This link will explain the answer
https://jersey.java.net/documentation/latest/media.html#d0e7963
9.1.1.1. POJO support
POJO support represents the easiest way to convert your Java Objects
to JSON and back.
Media modules that support this approach are MOXy and Jackson
The link to Jackson includes
9.1.4.1. Dependency
To use Jackson 2.x as your JSON provider you need to add
jersey-media-json-jackson module to your pom.xml file
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.2</version>
</dependency>
I don't know much about moxy but you had jackson on your CLASSPATH and were using Jackson annonations. Jersey however was configured to use moxy.
From link
JSON binding support via MOXy is a default and preferred way of
supporting JSON binding in your Jersey applications since Jersey 2.0.
When JSON MOXy module is on the class-path, Jersey will automatically
discover the module and seamlessly enable JSON binding support via
MOXy in your applications.
MOXy seemed to have handled FMSResponseInfo. Why it didn't handle the other object I do not know. But since you wanted to use Jackson you should have been using the Jackson module.

As suggested by Shire Resident in the comments using the following dependency I was able to resolve the problem :
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.2</version>
</dependency>

Related

Jackson doesn't unmarshall xml correctly in Spring

I'd like to write a rest service that accepts and produces XML or JSON based on the information from headers. To do so, I followed one of tutorials. The problem is that when I try to read fields of dto in Spring controller, they are all set to null.
For test purposes, I send in body a DTO and in controller I return it concatenating string Changed to two of its fields.
In body I send:
<?xml version="1.0" encoding="UTF-8"?>
<name>name</name>
<description>description</description>
However, I receive:
<Dto name="null Changed" description="null Changed"/>
I send request by Postman:
Here's my configuration:
Controller
#RestController
public class Controller {
#RequestMapping(value = "/endpoint", method = RequestMethod.POST,
consumes = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE},
produces = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE})
public Dto getAndReturnEntity(#RequestBody Dto dto) {
dto.setName(dto.getName() + " Changed");
dto.setDescription(dto.getDescription() + " Changed");
return dto;
}
}
DTO
#JacksonXmlRootElement
public class Dto {
#JacksonXmlProperty(isAttribute = true) // I also tried without it
private String name;
#JacksonXmlProperty(isAttribute = true)
private String description;
// getters and setters ommited for brevity
}
Configuration for Spring
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(false).
parameterName("mediaType").
ignoreAcceptHeader(false).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
Relevant part of pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
The input XML should be like this:
<Dto>
<name>abcd</name>
<description>desc</description>
</Dto>
In case you want the tag names be different than you can use either custom object mapper or add #JsonProperty.

Rest service won't work when using CDI: Service stays null

I am making a rest service application with JAX-RS. Its for some project for school. For this project I need to use follow techniques:
• Maven
• JAX-RS
• CDI
• JPA - EJB
• JNDI
• Bean Validation
So now I already maded my domain "Cafes" with a Fake DB ("CafeStub") and a real DB using JPA ("CafeDB"). My domain also makes a little usage of CDI. (#Inject in the CafeService class ...)
Non I wanted to create my rest service, using JAX-RS. This worked fine:
My problem is when I try to use CDI again it fails and it gives an 500 exception, NullPointerException, "Severe: The RuntimeException could not be mapped to a response, re-throwing to the HTTP container"
Full Stacktrace:
I don't know how to fix this, already searched a long time .. Hopefully somebody can help me :s
This is my "CafeController" class. Producing the rest service
Path("/cafes")
public class CafeController {
#Inject
private CafeFacade cafeFacade;
public CafeController() {
//this.cafeFacade = new CafeService();
}
#GET
#Produces("application/json")
public Response getCafes(){
try{
// test ........
ObjectMapper mapper = new ObjectMapper();
Cafe cafe = cafeFacade.getCafe(new Long(1));
String jsonInString = mapper.writeValueAsString(cafe);
return Response.status(200).entity(jsonInString).build();
}catch (JsonProcessingException jsonEx) {
System.out.println("Json Exception");
System.out.println(jsonEx.getMessage());
return null;
}
}
This one is the "CafeService" class, the one who implemented "CafeFacade"
public class CafeService implements CafeFacade {
#Inject
private CafeRepository cafeRepository;
public CafeService() {
//cafeRepository = new CafeStub();
//cafeRepository = new CafeDB("CafesPU");
}
#Override
public long addCafe(Cafe cafe) {
return this.cafeRepository.addCafe(cafe);
}
#Override
public Cafe getCafe(long cafeID) {
return this.cafeRepository.getCafe(cafeID);
}
Her you see the "CafeStub" class, the one who implemented "CafeRepository"
public class CafeStub implements CafeRepository {
private static Map<Long, Cafe> cafes;
private static long counter = 0;
public CafeStub() {
cafes = new HashMap<Long, Cafe>();
// adding some dara
this.addSomeData();
}
#Override
public long addCafe(Cafe cafe) {
if(cafe == null){
throw new DBException("No cafe given");
}
counter++;
cafe.setCafeID(counter);
cafes.put(cafe.getCafeID(), cafe);
return cafe.getCafeID();
}
#Override
public Cafe getCafe(long cafeID) {
if(cafeID < 0){
throw new DBException("No correct cafeID given");
}
if(!cafes.containsKey(cafeID)){
throw new DBException("No cafe was found");
}
return cafes.get(cafeID);
}
At least here you can see my pom.xml (dependencies from CafeService project) - web.xml (from CafeService project) and project structure ...
<dependencies>
<dependency>
<groupId>Cafes</groupId>
<artifactId>Cafes</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
<version>1.19.4</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.19.4</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.19.4</version>
</dependency>
</dependencies>
Thanks in advance ...
Cheers
Tom
A class annotated with just #Path does not mark the class as a CDI bean as it is not in the list of bean defining annotations in the CDI spec. Adding RequestScoped to the REST service marks it as a CDI bean so injection works as you've discovered.
This answer here lists the annotations which mark a class as a CDI bean.
Is #javax.annotation.ManagedBean a CDI bean defining annotation?
Solved .. RequestScoped did the trick.. Daimn searched so long for one annotation.
#RequestScoped
#Path("/cafes")
public class CafeController {
Still I don't understand why I need to use it.
#RequestScoped : CDI instantiates and manages the bean
-> I thought my bean.xml would have instantiates and manages the bean ?

Guice, Jetty, Jersey+Jackson BIG PLUS: Bean validation

I use guice, jetty, jersey+jackson in my stack to run a restful app. It works perfectly.
Then, I tried to add Jersey's Bean validation but I got no errors, no warnings... and no validation. I've read many articles, but non of these helped me out.
Here is my JerseyConfigModule:
public class JerseyConfigModule extends ServletModule {
#Override
protected void configureServlets() {
Map<String, String> initParams = new HashMap<String, String>();
initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
bind(GuiceContainer.class);
Set<Class<?>> classes=new ResourceConfig().property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE,true).getClasses();
for (Class<?> aClass : classes) {
bind(aClass);
}
serve("/rest/*").with(GuiceContainer.class, initParams);
}
}
My Jersey resource:
#Path("/user")
public class UserResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response post(#Valid StoreUserDTO user){
}
}
In an another Guice module I bind this resource:
bind(UserResource.class);
The Bean used in parameter:
public class StoreUserDTO {
#NotNull
private String name;
#NotNull
private String email;
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
I use jersey-guice and glassfish's jersey-bean-validation (and I want this):
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-guice</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
<version>2.22.1</version>
</dependency>
What did I forget? Lots of examples I've found is not worked or wasn't for Jersey 2, just for 1.
Yes, I know Jersey supports the bean validation officially, but in the offical docs, I didn't find any info about how to integrate with Guice.

Jersey + Jetty + JSON

I would like to produce an JSON using Jetty + Jersey.
My POM.XML is similar to this post: How do I update example to work with latest versions Jetty (9.1.0.RC2) and Jersey (2.7)?. I imagine that i am missing some dependecy. The result from inferFile() is returning blank.
I can see that the method toStirng from Student class was not been called.
Maven
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.1.3.v20140225</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.1.3.v20140225</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>2.14</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.14</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-jetty-http</artifactId>
<version>2.14</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.14</version>
</dependency>
Java
public class Student {
public Student(){
}
#Override
public String toString(){
return new StringBuffer(" First Name : ").append("MY FIRST NAME").toString();
}
}
#Path("/bulkload")
public class BulkLoadAPI {
#POST
#Path("inference")
#Consumes(MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_JSON)
public Student inferFile() throws URISyntaxException, IOException {
Student s = new Student();
return s;
}
}
public static void main(String[] args) throws Exception {
ServletHolder jerseyServlet = new ServletHolder(ServletContainer.class);
jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", "service.api.BulkLoadAPI");
jerseyServlet.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
Server server = new Server(10500);
ServletContextHandler context = new ServletContextHandler (server, "/", Servl etContextHandler.SESSIONS);
context.addServlet(jerseyServlet, "/*");
server.start();
server.join();
}
I'm not really sure what you're expecting. If you're expecting toString() to be called (which it isn't), that wouldn't even produce valid JSON. The way POJO to JSON (and vice versa) conversion is done is through MessageBodyReaders and MessageBodyWriters. Basically they are looking for fields with either some form of annotation known to the marshaller/unmarshaller, or Java bean style getters and setters. That's how the data/properties for the JSON will be discovered.
For example, if your Student looked like
public class Student {
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
it would give you JSON like {"firstName":"MyFirstName"}. You just need to set the property
public Student inferFile() throws URISyntaxException, IOException {
Student s = new Student();
s.setFirstName("MyFirstNaem");
return s;
}
Another thing, this is not needed
setInitParameter("com.sun.jersey.api.json.POJOMappingFeature","true");
That is a Jersey 1 feature. You are using Jersey 2. See also
jersey.config.server.provider.packages
So you don't have to configure each class individually. The value should be a package. It will scan the package and sub packages for annotated classes.

how to use some indirection when unmarshalling json to java bean using Jersey using jaxb annotations

I'm trying to unmarshall some received json (from Jira restful web service).
Problem is: an "issue" has a "summary" property and a list of fields.
Summary is not present as an attribute in the received json, but as a value of the "fields" attribute. I insist on unmarshalling to this structure:
#XmlRootElement
class Issue {
String summary;
List<Field> fields;
// getters/setters and lots of other fields
}
Received JSON:
{
"expand":"html",
"self":"https://example.com/jira/rest/api/latest/issue/XYZ-1234",
"key":"XYZ-1234",
"fields":
{
"summary":
{
"name":"summary",
"type":"java.lang.String",
"value":"test 1234"
},
"customfield_10080":
{
"name":"Testeur",
"type":"com.atlassian.jira.plugin.system.customfieldtypes:userpicker"
},
"status":
{
"name":"status",
"type":"com.atlassian.jira.issue.status.Status",
"value":
{
"self":"https://example.com/jira/rest/api/latest/status/5",
"name":"Resolved"
}
},
...
},
"transitions":"https://example.com/jira/rest/api/latest/issue/XYZ-1234/transitions"
}
I don't want to use Jira's own client (too many dependencies which I don't want in my app).
edit: I asked my question another way to try to make it clear: how to map a bean structure to a different schema with jax-rs
Your annotated class should be bijective: it should allow to generate the same input from which it was unmarshalled. If you still want to use a non-bijective object graph, you can use #XmlAnyElement the following way:
public class Issue {
#XmlElement(required = true)
protected Fields fields;
public Fields getFields() {
return fields;
}
}
In the input you gave, fields is not a list, but a field (JSON uses [] to delimit lists):
public class Fields {
#XmlElement(required = true)
protected Summary summary;
#XmlAnyElement
private List<Element> fields;
public List<Element> getFields() {
return fields;
}
public Summary getSummary() {
return summary;
}
}
In order to catch Summary, you'll have to define a dedicated class. Remaining fields will be grouped in the fields list of elements.
public class Summary {
#XmlAttribute
protected String name;
public String getName() {
return name;
}
}
Below, a unit test using your input shows that everything work:
public class JaxbTest {
#Test
public void unmarshal() throws JAXBException, IOException {
URL xmlUrl = Resources.getResource("json.txt");
InputStream stream = Resources.newInputStreamSupplier(xmlUrl).getInput();
Issue issue = parse(stream, Issue.class);
assertEquals("summary", issue.getFields().getSummary().getName());
Element element = issue.getFields().getFields().get(0);
assertEquals("customfield_10080", element.getTagName());
assertEquals("name", element.getFirstChild().getLocalName());
assertEquals("Testeur", element.getFirstChild().getFirstChild().getTextContent());
}
private <T> T parse(InputStream stream, Class<T> clazz) throws JAXBException {
JSONUnmarshaller unmarshaller = JsonContextNatural().createJSONUnmarshaller();
return unmarshaller.unmarshalFromJSON(stream, clazz);
}
private JSONJAXBContext JsonContextNatural() throws JAXBException {
return new JSONJAXBContext(JSONConfiguration.natural().build(), Issue.class);
}
}
This tests shows that without using dedicated classes, your code will quickly be hard to read.
You will need those maven dependencies to run it:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>r08</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.6</version>
</dependency>
{
"expand":"html",
"self":"xxx/jira/rest/api/latest/issue/EPC-2731";,
"key":"EPC-2731",
"fields":{
"summary":{
"name":"summary",
"type":"java.lang.String",
"value":"Fwd: commentaires vides dans FicheSousGroupement"
},
"timetracking":{
"name":"timetracking",
"type":"com.atlassian.jira.issue.fields.TimeTrackingSystemField",
"value":{
"timeestimate":0,
"timespent":60
}
},
"issuetype":{
"name":"issuetype",
"type":"com.atlassian.jira.issue.issuetype.IssueType",
"value":{
"self":"xxx/jira/rest/api/latest/issueType/2";,
"name":"Nouvelle fonctionnalité",
"subtask":false
}
},
"customfield_10080":{
"name":"Testeur",
"type":"com.atlassian.jira.plugin.system.customfieldtypes:userpicker"
},

Categories