Reputation: 149
I'm using Jersey 2.4 to create a simple REST interface that serves up a JSON object. My problem is that I'm trying to use the fasterxml Jackson annotations to control the output and this is not working for me. I have put the annotations into my bean class but they are ignored.
When I explicitly create an ObjectMapper and use this to stringify the Java bean, I get the output that I want, which respects the Jackson annotations. However, I would prefer that I don't have to do this step so that my resource class can simply return the bean and the Jersey framework takes care of stringifying it.
I have tried to solve this using the answer from Custom ObjectMapper with Jersey 2.2 and Jackson 2.1, however, this does not appear to work for me. I see that the ContextResolver is created but it is never called.
I have also spent many hours trying to solve this apparently simple problem. I have stripped this down to a very simple test case, which is shown below. I would appreciate any help at all in resolving this.
Resource Java class:
@Path("resource")
public class MainResource {
public static class Foobar {
@JsonIgnore
private String foo = "foo";
private String baa = "baa";
private Map<String, List<? extends Number>> map = new HashMap<>();
public Foobar() {
map.put("even", Arrays.asList(new Integer[] { 2, 4, 6, 8, 10 }));
map.put("odd", Arrays.asList(new Integer[] { 1, 3, 5, 7, 9 }));
map.put("float", Arrays.asList(new Float[] { 1.1F, 2.2F, 3.3F }));
}
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBaa() {
return baa;
}
public void setBaa(String baa) {
this.baa = baa;
}
@JsonAnyGetter
public Map<String, List<? extends Number>> getMap() {
return map;
}
public void setMap(Map<String, List<? extends Number>> map) {
this.map = map;
}
}
private ObjectMapper om = new ObjectMapper();
@GET
@Path("get-object")
@Produces(MediaType.APPLICATION_JSON)
public Foobar getObject() {
// In this method, I simply return the bean object but the WRONG JSON syntax is generated.
return new Foobar();
}
@GET
@Path("get-string")
@Produces(MediaType.APPLICATION_JSON)
public String getString() throws JsonProcessingException {
// This method returns the RIGHT JSON syntax but I don't want to have to explicitly use the ObjectMapper.
Foobar foobar = new Foobar();
return om.writeValueAsString(foobar);
}
}
web.xml:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<module-name>sample</module-name>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>ie.cit.nimbus.sample</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
POM dependencies:
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.4.1</version>
</dependency>
</dependencies>
Upvotes: 14
Views: 46347
Reputation: 35224
EDIT: Don't use the old approach below as it produces bugs (at least with android device, see EDIT2 for more details). As of my tests, Jersey v2.6 seems to fix the problem with the @Provide
, which approach did not work. I was able to get it work with this simple provider:
@Provider
public class JerseyMapperProvider implements ContextResolver<ObjectMapper> {
private static ObjectMapper apiMapper = ObjectMapperManager.createMapperForApi();
@Override
public ObjectMapper getContext(Class<?> type) {
return apiMapper;
}
}
So please don't use my hack from below.
OLD APPROACH
Using
@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>
was not working for me (Jersey 2.4 & Jackson 2.3) and maybe this is due to an in the jackson provider reported bug in the code where the ContextResolver
should be registered in JacksonJsonProvider.java
(2.3rc1):
@Override
protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType) {
if (_providers != null) {
ContextResolver<ObjectMapper> resolver = _providers.getContextResolver(ObjectMapper.class, mediaType);
/* Above should work as is, but due to this bug
* [https://jersey.dev.java.net/issues/show_bug.cgi?id=288]
* in Jersey, it doesn't. But this works until resolution of
* the issue:
*/
if (resolver == null) {
resolver = _providers.getContextResolver(ObjectMapper.class, null);
}
if (resolver != null) {
return resolver.getContext(type);
}
}
return null;
}
But at least I cannot access https://jersey.dev.java.net/issues/show_bug.cgi?id=288, so I don't know what this bug is about.
However, I found a workaround (a hack if you so will). Just extend JacksonJsonProvider
with the proper annotation and return your ObjectMapper
like this:
@Provider
@Consumes(MediaType.APPLICATION_JSON) // NOTE: required to support "non-standard" JSON variants
@Produces(MediaType.APPLICATION_JSON)
public class JacksonHackProvider extends JacksonJsonProvider {
@Override
protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType) {
return new MyCustomObjectMapper();
}
}
No need to do anything it will register itself (check with log, it will register the first time you access a JSON rest service). This is now working for me, not elegant, but I gave up.
EDIT: Use with caution - I'm experiencing a bug maybe related to this hack: Android volley cannot send a POST/PUT request with a request body, always getting 400 from the framework, I will investigate and report my findings.
EDIT2: This hack was indeed responsible for a generic 400 whenever an Android app with volley and OKHTTP
client tried to do a POST or PUT request so don't use this - in my test jersey 2.6 seems to fix this, so you can use @Provide
approach
Upvotes: 13
Reputation: 174
add on each model class like this
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Event {
-----
--
}
it has worked for me but i have used codehus jackson jars
Upvotes: -1
Reputation: 3833
Using Jersey 2.13
, you can force the @Provider
to use the same ObjectMapper
should only create one ObjectMapper
:
package com.example.api.environment.configs;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.registerModule(new JodaModule());
};
@Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
I use this to @Autowire
in my ObjectMapper
to generate json-schema
documents on the fly.
Upvotes: 7
Reputation: 4914
Unfortunately everyone makes this much harder than it needs to be. The Jersey team in their wisdom decided to integrate Jackson 1.9, so their stuff wont' help you.
But it was pretty easy for me. Just do this:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.3.0</version>
</dependency>
Now GET RID OF THIS:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.4.1</version>
</dependency>
Then in your web.xml change this line:
<param-value>ie.cit.nimbus.sample</param-value>
To be:
<param-value>ie.cit.nimbus.sample,com.fasterxml.jackson.jaxrs.json</param-value>
That should do it.
Upvotes: 9
Reputation: 2406
There are a lot of ways to integrate jackson with Jax-rs Jersey implementation.
If you take a look to Mkyong tutorial: http://www.mkyong.com/webservices/jax-rs/json-example-with-jersey-jackson/ It seems that you should also pass the "POJOMappingFeature" -> true in the init params in web.xml. I think this works for Jersey 1.8
If you take a look to official Jersey documentation instead: https://jersey.java.net/nonav/documentation/latest/user-guide.html#json.jackson It seems that you should implements a Jax-rs provider and add that provider to your application resources
@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>
They provide you an example of how to do this https://github.com/jersey/jersey/blob/2.4.1/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyObjectMapperProvider.java
I used this way and that solved my problems, and Jackson annotations are correctly scanned by Jackson provider.
Off topic I suggest you to use this syntax in your bean to initialize map:
import static java.util.Arrays.asList;
public static class Foobar {
@JsonIgnore
private String foo = "foo";
private String baa = "baa";
private Map<String, List<? extends Number>> map = new HashMap<>(){{
put("even", asList(2, 4, 6, 8, 10));
put("odd", asList(1, 3, 5, 7, 9));
put("float", asList(1.1F, 2.2F, 3.3F));
}};
public Foobar() {
}
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBaa() {
return baa;
}
public void setBaa(String baa) {
this.baa = baa;
}
@JsonAnyGetter
public Map<String, List<? extends Number>> getMap() {
return map;
}
public void setMap(Map<String, List<? extends Number>> map) {
this.map = map;
}
}
Upvotes: 1