Michał Wróbel
Michał Wróbel

Reputation: 558

How to use two versions of Jackson (1.x and 2.x) in the same Jersey JAX-RS application?

I work in a project with an old version of Jersey (1.19) along with old Jackson (1.19.13). I would like to switch, to new Jackson (2.x) deserialization, but only for new endpoints (and old ones gradually) because migration in one step to Jackson 2 and/or Jersey 2 would be really difficult (oh, the monolith!).

I've seen some topics about how to provide custom-configured ObjectMapper with Jackson providers in Jersey, or how to install new Jackson in 1.x Jersey but that's not I'm looking for, at least not all.

What I imagine as a solution, is (preferably) annotating my new JAX-RS endpoint with something like @UseJackson2 , or having some base class with some magic retrieving ObjectMapper from the correct package, for this particular endpoint and extending it later - in other words forcing given endpoint to use other (de)serialization provider than normally.

I've seen examples with providers for differently configured ObjectMappers (like here Using Jackson in Jersey with multiple configured ObjectMappers), but in my case, the ObjectMappers would come from completely different packages/maven artifacts.

Upvotes: 4

Views: 2807

Answers (2)

Leon
Leon

Reputation: 3244

Based on the solution of Paul Samsotha, creaing 2 providers worked fine for me.

public class Main {
  public static void main(final String[] args) {
    ResourceConfig config = new ResourceConfig();
    config.register(Service1.class)
        .register(Service2.class)
        .register(JacksonV1Provider.class)
        .register(JacksonV2Provider.class);
    GrizzlyHttpServerFactory.createHttpServer(URI.create("http://0.0.0.0:8123"), config);
  }
}

@Provider
public class JacksonV1Provider extends org.codehaus.jackson.jaxrs.JacksonJsonProvider {
  @Context
  private ExtendedUriInfo uriInfo;

  @Override
  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v1/");
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v1/");
  }
}

@Provider
public class JacksonV2Provider extends com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider {
  @Context
  private ExtendedUriInfo uriInfo;

  @Override
  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v2/");
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return uriInfo.getPath().contains("/v2/");
  }
}

dependencies {
    compile group: 'org.glassfish.jersey.containers', name: 'jersey-container-grizzly2-http', version: '2.17'
    compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.17'

    compile group: "org.codehaus.jackson", name: "jackson-mapper-asl", version: "$jackson1_version"
    compile group: "org.codehaus.jackson", name: "jackson-jaxrs", version: "$jackson1_version"
    compile group: "org.codehaus.jackson", name: "jackson-xc", version: "$jackson1_version"

    compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson2_vervion"
    compile "com.fasterxml.jackson.core:jackson-core:$jackson2_vervion"
    compile "com.fasterxml.jackson.core:jackson-annotations:$jackson2_vervion"
}

Upvotes: 1

Paul Samsotha
Paul Samsotha

Reputation: 208964

What you can do is create a MessageBodyReader/Writer that handles the 2.x version of Jackson. The isReadable and isWritable methods determine which entities it can handle. What you can do to check is inject Jersey's ExtendedUriInfo into the provider and check the resource class for your @Jackson2 annotation. If the annotation is not present than your provider ignores the entity and the runtime moves on to the next provider and checks if it can handle it; in this case the 1.x provider.

Instead of completely creating your own, you would just extend the provider that Jackson already provides in it artifact

<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.9.2</version>
</dependency>

You would extend the JacksonJaxbJsonProvider.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Jackson2 {
}

@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MyProvider extends JacksonJaxbJsonProvider {

    @Context
    private ExtendedUriInfo info;

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        if (!info.getMatchedMethod().getDeclaringResource().isAnnotationPresent(Jackson2.class)) {
            return false;
        }
        return super.isReadable(type, genericType, annotations, mediaType);
    }



    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        if (!info.getMatchedMethod().getDeclaringResource().isAnnotationPresent(Jackson2.class)) {
            return false;
        }
        return super.isWriteable(type, genericType, annotations, mediaType);
    }
}

Then just annotate your resource classes with @Jackson2.

I had problems earlier while trying to get this to work, because I was using Jackson 2.8.4. It seams the whole 2.8 line has a problem. Not sure what it is, but with 2.8, my provider would not register at all. I tested with minor versions 2.2-2.9 (excluding 2.8) and they all work.

As far as priority, I'm not sure about how Jersey determines precedence. If the 1.x provider were to be called first, then this solution would all fall apart. One way around this would be to use composition instead of inheritance, where you would Just determine which reader/writer (1.x or 2.x) to use inside your readFrom and writeTo methods. The 1.x version is also JacksonJaxbJsonProvider but it used the codehaus packaging. From what I tested though, my provider always gets called first, so this may not be needed. Unfortunately, I cannot confirm that this is by design.

Upvotes: 3

Related Questions