Custom Objectmapper with Jersey 2.2 and Jackson 2.1

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:

Sample Image

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:

  1. Disable the auto-discovery (as mention in the previous link)1

  2. Don't use the jersey-media-json-jackson. Just use the Jackson native module jackson-jaxrs-json-provider. Thing about this though is that, the jersey-media-json-jackson adds a couple features on top of the the native module, so you would lose those.

  3. Haven't tested, but it seems that if you use JacksonJaxbJsonProvider instead of JacksonJsonProvider, it might work. If you look at the source for the JacksonFeature, you will see that it checks for an already registered JacksonJaxbJsonProvider. 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



Leave a reply



Submit