10

I have a jersey2 application configured for JSON support via Jackson, adding

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey.version}</version>
</dependency>

in the POM file and

public MyApplication() {
    ...
    register(JacksonFeature.class)
    ...
}

in my application. Everything works, my resources get deserialized POJOs as arguments

@POST @Consumes(MediaType.APPLICATION_JSON)
public void blah(MyPojo p) {
    ...
}

Now one of thoese resources needs a reference to Jackson's ObjectMapper to do some deserialization on its own. I've tried doing something like

@Inject
public MyResource(@Context ObjectMapper mapper) {
    ...
}

or

@GET
public String foo(@Context ObjectMapper mapper) {
    ...
}

but in both cases the reference to mapper is null. How can I inject a reference to the ObjectMapper in my resources?

agnul
  • 12,608
  • 14
  • 63
  • 85

2 Answers2

6

First there is no default ObjectMapper used by the Jackson provider. It doesn't use an ObjectMapper at all actually. It makes use of other Jackson APIs to handle the (de)serialization.

If you want to use/inject a single ObjectMapper instance, then you should just create a Factory for it

public class ObjectMapperFactory implements Factory<ObjectMapper> {

    final ObjectMapper mapper = new ObjectMapper();

    @Override
    public ObjectMapper provide() {
        return mapper;
    }

    @Override
    public void dispose(ObjectMapper t) {}   
}

Then bind it

register(new AbstractBinder(){
    @Override
    public void configure() {
        bindFactory(ObjectMapperFactory.class)
            .to(ObjectMapper.class).in(Singleton.class);
    }
});

One thing should be noted is that any configuration of the ObjectMapper is not thread safe. So say you tried to configure it from your resource method, those operations are not thread safe.

Another thing to note with the Jackson provider, is that if we provide a ContextResolver, like mentioned by @Laurentiu L, then the Jackson provider will switch to using our ObjectMapper. In which case, if you want to use that same ObjectMapper, then you can look it up in the Factory. For example

public class ObjectMapperFactory implements Factory<ObjectMapper> {

    private final Providers providers;
    final ObjectMapper mapper = new ObjectMapper();

    public ObjectMapperFactory(@Context Providers providers) {
        this.providers = providers;
    }

    @Override
    public ObjectMapper provide() {
        ContextResolver<ObjectMapper> resolver = providers.getContextResolver(
                ObjectMapper.class, MediaType.APPLICATION_JSON);
        if (resolver == null) { return mapper; }

        return resolver.getContext(null);
    }

    @Override
    public void dispose(ObjectMapper t) {}   
}

For the above to work (use a single ObjectMapper), you need to make sure to implement the ContextResolver<ObjectMapper>, and make sure to annotation the ContextResolver with the corresponding @Produces and @Consumes media types.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • 1
    according to jackson documentation ObjectMapper intances are thread-safe as long as no reconfiguration takes place. "Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls." https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/ObjectMapper.html – Ricard Nàcher Roig Jun 14 '19 at 09:15
  • @RicardNàcherRoig Isn't that what I said? _"One thing should be noted is that any configuration of the ObjectMapper is not thread safe"_. If not worded correctly, that's what I meant. You should not try to reconfigure the mapper at runtime; That same mapper may be being used for another request. – Paul Samsotha Jun 14 '19 at 10:22
  • Thank @paul-samsotha for your response. In my opinion, It is not worded correctly or at least I think that the explanation from Jackson API documentation is by far more clear. I wasted some time thinking that ObjectMapper was not ever thread-safe. – Ricard Nàcher Roig Jun 14 '19 at 10:58
4

Aside from the JacksonFeature you need to register a ContextResolver for ObjectMapper.

Simple example from the Documentation at 9.1.4.2. Configure and register

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {

    final ObjectMapper defaultObjectMapper;

    public MyObjectMapperProvider() {
        defaultObjectMapper = createDefaultMapper();
    }

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

    private static ObjectMapper createDefaultMapper() {
        final ObjectMapper result = new ObjectMapper();
        result.configure(Feature.INDENT_OUTPUT, true);

        return result;
    }

    // ...
}

Complete code example available on Github

You will also need to register it

        .register(MyObjectMapperProvider.class)  
Martin
  • 2,573
  • 28
  • 22
Laurentiu L.
  • 6,566
  • 1
  • 34
  • 60
  • 1
    That works, but since I have no need (yet?) of a custom provided `ObjectMapper` I was hoping for a way to use Jersey's created one instead of providing my own. – agnul May 15 '15 at 12:13
  • If anyone is having "problem: abstract types either need to be mapped to concrete types" with Dropwizard even though you registered a module with `addAbstractTypeMapping`, this solution fixed the issue like a charm (just register your module in `createDefaultMapper` and register this `Provider` with `environment.jersey().register()` in the `run()` of your `Application`)! – qwertzguy Apr 19 '17 at 03:51