Garret Wilson
Garret Wilson

Reputation: 21496

Jackson 2 incorrectly serializing Java java.nio.file.Path

I'm using RESTEasy 3.1.0.CR3 with Tomcat 8.5.6 inside Eclipse 4.6.1, with the JSBoss resteasy-jackson2-provider. I have a simple JavaBean FooBar that returns a string "ID" and a java.nio.file.Path "path".

Jackson makes this easy to serialize to JSON. In my JAX-RS resource I simply specify @Produces("application/json; charset=UTF-8"). But Jackson is not using Path.toString(). Instead it appears to be using Path.toURI().toString() or something:

{
  "id": "foo",
  "path": "file:///C:/Users/jdoe/bar"
}

Why!?? And more importantly, how can I get Jackson to simply use the toString() version of Path?

Here is my project dependency tree:

+- com.google.code.findbugs:jsr305:jar:3.0.1:provided
+- com.google.guava:guava:jar:20.0:compile
+- javax.ws.rs:javax.ws.rs-api:jar:2.0.1:provided
+- org.jboss.resteasy:resteasy-jaxrs:jar:3.1.0.CR3:compile
|  +- org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.0_spec:jar:1.0.1.Beta1:compile
|  +- org.jboss.resteasy:resteasy-jaxrs-services:jar:3.1.0.CR3:compile
|  +- org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:jar:1.0.0.Final:compile
|  +- javax.activation:activation:jar:1.1.1:compile
|  +- org.apache.httpcomponents:httpclient:jar:4.5.2:compile
|  |  +- org.apache.httpcomponents:httpcore:jar:4.4.4:compile
|  |  +- commons-logging:commons-logging:jar:1.2:compile
|  |  \- commons-codec:commons-codec:jar:1.9:compile
|  +- commons-io:commons-io:jar:2.5:compile
|  +- net.jcip:jcip-annotations:jar:1.0:compile
|  \- org.jboss.logging:jboss-logging:jar:3.3.0.Final:compile
+- org.jboss.resteasy:resteasy-servlet-initializer:jar:3.1.0.CR3:compile
+- org.jboss.resteasy:resteasy-jackson2-provider:jar:3.1.0.CR3:compile
|  +- com.fasterxml.jackson.core:jackson-core:jar:2.8.3:compile
|  +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.3:compile
|  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.3:compile
|  \- com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:jar:2.8.3:compile
|     +- com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:jar:2.8.3:compile
|     \- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.8.3:compile
+- junit:junit:jar:4.12:test
|  \- org.hamcrest:hamcrest-core:jar:1.3:test
\- org.hamcrest:hamcrest-library:jar:1.3:test

Note that it is not acceptable for me to add annotations to my FooBar class, which is a domain model class that should have no coupling with the RESTful API serialization details.

I want a simple way to hook into Jackson2 in RESTEasy and modify the serialization of a Path property value without modifying my class or writing a custom serializer for my class.

Upvotes: 4

Views: 2415

Answers (1)

Shashank
Shashank

Reputation: 416

This class will override default Jackson2JsonProvider help you to configure ObjectMapper, according to you needs, like serializer for Path.class is overriden to ToStringSerializer from NioPathSerializer.

@Provider
@Consumes({"application/*+json", "text/json"})
@Produces({"application/*+json", "text/json"})
public class CustomJacksonProvider extends JacksonJsonProvider {

  public CustomJacksonProvider(){
    super(configureMapper(new ObjectMapper()));
  }

  public static ObjectMapper configureMapper(ObjectMapper mapper) {
    SimpleModule m = new SimpleModule("PathToString");
    m.addSerializer(Path.class,new ToStringSerializer());
    mapper.registerModule(m);
    return mapper;
  }

}

Or you can use ContextResolver also for the same which will use ResteasyJackson2Provider(RestEasy default)

@Provider
@Consumes({"application/*+json", "text/json"})
@Produces({"application/*+json", "text/json"})
public class CustomJacksonProvider implements ContextResolver<ObjectMapper> {

  final ObjectMapper mapper;

  public CustomJacksonProvider(){
    mapper = new JsonMapperConfigurator(new ObjectMapper(), JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS)
            .getConfiguredMapper();
    SimpleModule m = new SimpleModule("PathToString");
    m.addSerializer(Path.class, new ToStringSerializer());
  }

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return mapper;
  }
}

Add this to you web.xml irrespective of approch you use:

    <context-param>
       <param-name>resteasy.providers</param-name>
       <param-value>package.to.CustomJacksonProvider</param-value>
    </context-param>

If you are not using a web.xml file, return the provider class in your JAX-RS application's Application.getClasses() method.

Hope this helps.

Edited:

Also, you might miss JAXB annotation support in this implementation you have to configure that manually, because JacksonJaxbJsonProvider doesn't look for Resolver/provider of JsonMapperConfigurator. Use JsonMapperConfiguration with mapper in CustomJackosnProvider along with JacksonJaxbJsonProvider.DEAFULT_ANNOATIONS. Ideally, since there is no provision to provide JsonMapperConfiguration Resolver/Provider and RestEasy's jackson provider doesn't expose arg constructor, provider is best bet. Since they taken this decision, ideally they should not do any customization in ResteasyJackson2Provider. Also, only functionality in ResteasyJackson2Provider is custom json type and better caching of mapper at class level.

Updated code according to explanation

Upvotes: 4

Related Questions