Custom ObjectMapper with Jersey 2.2 and Jackson 2.1
I found a solution. I had to instantiate the Jackson Provider by myself and set my custom ObjectMapper
. A working example can be found on GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer
I deleted my ObjectMapperResolver
and modified my main
method:
public class Main {
public static void main(String[] args) {
try {
// create custom ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// create JsonProvider to provide custom ObjectMapper
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(mapper);
// configure REST service
ResourceConfig rc = new ResourceConfig();
rc.register(ExampleResource.class);
rc.register(provider);
// create Grizzly instance and add handler
HttpHandler handler = ContainerFactory.createContainer(
GrizzlyHttpContainer.class, rc);
URI uri = new URI("http://0.0.0.0:8080/");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
ServerConfiguration config = server.getServerConfiguration();
config.addHttpHandler(handler, "/");
// start
server.start();
System.in.read();
} catch (ProcessingException | URISyntaxException | IOException e) {
throw new Error("Unable to create HTTP server.", e);
}
}
}
Jersey Ignoring Custom ObjectMapper
It's working now. Jersey is now recognizing the custom ObjectMapper, which is configured to ignore unknown JSON fields with "FAIL_ON_UNKNOWN_PROPERTIES, false".
The ObjectMapper code above is correct. The problem (as suggested by Paul in the comments) was that the client had not registered the custom ObjectMapper. This was fixed very simply, by adding the following line to the client setup method, following client setup with ClientBuilder.
this.client.register(OutMapperProvider.class);
Using customized ObjectWriter with Jersey
Since version 2.6 of Jackson it has the 'setFilterProvider' method for an ObjectMapper.
I didn't try it but the documentation has the description for this: https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/ObjectMapper.html#setFilterProvider-com.fasterxml.jackson.databind.ser.FilterProvider-. You can try i think because the description fits for your case.
I built a test service with Jersey 2.7 and Jackson 2.9.5. it works fine but you have to know some tricks to run it.
In pom.xml add Jersey and Jackson:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<properties>
<jersey.version>2.7</jersey.version>
<jackson.version>2.9.5</jackson.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
You have to define this dependence:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
it's mandatory.
In web.xml you have to make the ref to configuration of your service:
<web-app version="2.5"
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_2_5.xsd">
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>org.glassfish.jersey.server.ResourceConfig</param-name>
<param-value>com.home.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
MyApplication.java:
package com.home;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.ApplicationPath;
@ApplicationPath("/webapi")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(ObjectMapperProvider.class);
register(JacksonFeature.class);
register(MyResource.class);
}
}
With a custom ObjectMapperProvider you have to register a JacksonFeature.class because without it Jersey doesn't use the custom ObjectMapperProvider.
ObjectMapperProvider.java:
package com.home;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
final ObjectMapper defaultObjectMapper;
public ObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
@Override
public ObjectMapper getContext(Class<?> type) {return defaultObjectMapper;}
public static ObjectMapper createDefaultMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.setFilters(new SimpleFilterProvider().addFilter("dataFilter", SimpleBeanPropertyFilter.serializeAllExcept("region", "city")));
return mapper;
}
}
To define a filter use the 'setFilters' methods. This method is deprecated but the Jersey's library which called 'jersey-hk2' doesn't know the new method 'setFilterProvider' and throws an exception. With the old method everything works fine.
A business object with @JsonFilter:
@JsonFilter("dataFilter")
public class SimpleData {
@JsonProperty("name")
String firstName;
@JsonProperty("secondName")
String lastName;
@JsonProperty("country")
String country;
@JsonProperty("region")
String region;
@JsonProperty("city")
String city;
@JsonProperty("genre")
String genre;
public SimpleData() {
this.firstName = "Bryan";
this.lastName = "Adams";
this.country = "Canada";
this.region = "Ontario";
this.city = "Kingston";
this.genre = "Rock";
}
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getGenre() { return genre; }
public void setGenre(String genre) { this.genre = genre; }
}
MyResource.java:
@Path("myresource")
public class MyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public SimpleData getIt() {
return new SimpleData();
}
}
A filtered result:
Registering JacksonJsonProvider with ObjectMapper + JavaTimeModule to Jersey 2 Client
Eventually it works if I use solution that is commented in following snippet.
First of all, you are missing a dependency in your list, that you also have, which is the problem.
jersey-media-json-jackson
This module depends on the native Jackson module that has the JacksonJsonProvider
. When you register the JacksonFeature
(that comes with jersey-media-json-jackson
), it registers its own JacksonJaxbJsonProvider
, which seems to take precedence over any that you provide.
When you use the ContextResolver
, the JacksonJsonProvider
actually looks-up that ContextResolver
and uses it to resolve the ObjectMapper
. That's why it works. Whether you used the JacksonFeature
or registered your own JacksonJsonProvider
(without configuring an ObjectMapper
for it) the ContextResovler
would work.
Another thing about the jersey-media-json-jackson
module, it that it participates in Jersey's auto-discoverable mechanism, which registers it's JacksonFeature
. So even if you didn't explicitly register it, it would still be registered. The only ways to avoid it being registered are to:
Disable the auto-discovery (as mention in the previous link)1
Don't use the
jersey-media-json-jackson
. Just use the Jackson native modulejackson-jaxrs-json-provider
. Thing about this though is that, thejersey-media-json-jackson
adds a couple features on top of the the native module, so you would lose those.Haven't tested, but it seems that if you use
JacksonJaxbJsonProvider
instead ofJacksonJsonProvider
, it might work. If you look at the source for theJacksonFeature
, you will see that it checks for an already registeredJacksonJaxbJsonProvider
. If there is one, it won't register it's own.The one thing I'm not sure about with this is the auto-discoverable. The order in which it is registered, if it will affect whether or not it catches your registered
JacksonJaxbJsonProvider
. Something you can test out.
Footnotes
1. See also
Cannot get jersey 2 application to use custom jackson XmlMapper
So a few things. First you need more than just the core Jackson xml dependency, you need the actual jaxrs provider
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-xml-provider</artifactId>
<version>${jackson2.version}</version>
</dependency>
Then you should exclude the JAXB provider, which is the default provider used by Jersey. (I had not problems leaving it while testing, but if you're not going to use it, I would just exclude it). It is pulled in by jersey-server
, so you should explicitly declare the jersey-server
and exclude the jersey-media-jaxb
from it
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>${jersey2.version}</version>
<exclusions>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-jaxb</artifactId>
</exclusion>
</exclusions>
</dependency>
Then you will need to register the JacksonJaxbXMLProvider
(or just the JacksonXMLProvider
if you don't need or plan on using JAXB annotations).
public CCRestResources() {
register(JacksonFeature.class);
register(JacksonJaxbXMLProvider.class);
packages("com.cc.rest.jersey");
}
Then finally you need to parameterize the ContextResolver
as a XmlMapper
type, not ObjectMapper
. As seen here, the provider looks for a ContextResolver
for XmlMapper
, not ObjectMapper
.
JacksonJaxbJsonProvider default objectmapper mapping
This call
.setAnnotationIntrospector(createJaxbJacksonAnnotationIntrospector());
is what adds the JAXB support for the combinedObjectMapper
. So if you want the JAXB support for the defaultObjectMapper
, just add the same call.
final ObjectMapper result = new ObjectMapper();
result.setAnnotationIntrospector(createJaxbJacksonAnnotationIntrospector());
Custom Jackson 2 ObjectMapper doesn't always get used after Maven build?
To me it sounds like an maven dependencies issue, one time your maven uses version 1 of dep A the other time it uses version 2 of dep A. Best way is to check your dependency tree and explicitly exclude all unwanted version in your pom file.
Related Topics
Multiple Axes on the Same Data
Equivalent of Waitforvisible/Waitforelementpresent in Selenium Webdriver Tests Using Java
How to Emit and Handle Custom Events
How to Create an Instance of Inner Class Using Java Reflection
How to Create a Static Local Variable in Java
What Are Shadow Variables in Java
How to See Javadoc in Intellij Idea
Differencebetween Optional.Flatmap and Optional.Map
Eclipse - Java.Lang.Classnotfoundexception
How to Get a Random Line of a Text File in Java
Why Are New Java.Util.Arrays Methods in Java 8 Not Overloaded for All the Primitive Types
How to Add Days to a Date in Java
Initializing an Array in Java Using the 'Advanced' for Each Loop