Using Jackson in Jersey with multiple configured ObjectMappers
jackson, jackson-modules, jax-rs, jersey
Solution
You can configure the order of providers, but it would actually be best to use one provider in this situation:
@Provider
public class JacksonMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper defaultMapper;
private final ObjectMapper specializedMapper;
public JacksonMapperProvider() {
defaultMapper = createDefaultMapper();
specializedMapper = createSpecializedMapper();
}
private static ObjectMapper createDefaultMapper() {
return new ObjectMapper()
.setSerializationInclusion(Include.ALWAYS)
.configure(JsonParser.Feature.ALLOW_COMMENTS, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
}
private static ObjectMapper createSpecializedMapper() {
return new ObjectMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"))
.registerModule(new SpecializedModule1())
.registerModule(new SpecializedModule2());
}
@Override
public ObjectMapper getContext(Class<?> type) {
if (SomeType.isAssignableFrom(type)) {
return specializedMapper;
}
else {
return defaultMapper;
}
}
}
Problem
Is it possible to setup Jersey using Jackson for serialization/deserialization using multiple configured `ObjectMappers`? What I would like to be able to do is register a "default" Jackson `ObjectMapper` and then have the ability to register another feature which provides an `ObjectMapper` with some specialized configuration which under certain circumstance will "override" the "default" `ObjectMapper`. For example, this `ContextResolver` would be for the "default" mapper: ``` @Provider @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class JacksonMapperProvider implements ContextResolver<ObjectMapper> { private final ObjectMapper mObjectMapper; public JacksonMapperProvider() { mObjectMapper = createMapper(); } protected abstract ObjectMapper createMapper() { ObjectMapper mapper = createMapper(); return mapper .setSerializationInclusion(Include.ALWAYS) .configure(JsonParser.Feature.ALLOW_COMMENTS, true) .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) .configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); } @Override public ObjectMapper getContext(Class<?> type) { return mObjectMapper; } } ``` And this `ContextResolver` would be to override the "default" mapper: ``` @Provider @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class SpecializedMapperProvider implements ContextResolver<ObjectMapper> { private final ObjectMapper mObjectMapper; public SpecializedMapperProvider() { mObjectMapper = createMapper(); } protected abstract ObjectMapper createMapper() { ObjectMapper mapper = createMapper(); return mapper .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")) .registerModule(new SpecializedModule1()) .registerModule(new SpecializedModule2()); } @Override public ObjectMapper getContext(Class<?> type) { if(SomeType.isAssignableFrom(type)) { return mObjectMapper; } return null; } } ``` I see in the `JacksonJsonProvider` code that Jackson supports `ObjectMapper` provider injection/resolution. However, in practice, what I am seeing is that the "order" of the providers seems random (I'm guessing it's not, but I can't sort out how to control the order). Sometimes the "override" comes before the "default" and everything works, but on the next server startup the order changes. I have attempted to get this to work in a number of ways including: - Registering the `ContextResolver<ObjectMapper>` implementations manually (in differing orders) - Registering the `ContextResolver<ObjectMapper>` implementations via `@Provider` annotations - Specifying a priority when registering I am using the following: - Jersey 2.8 - Jackson 2.3.3 Perhaps I am taking a completely incorrect approach? Is there a better way to achieve what I am trying to do? Maybe I should just define two separate JAX-RS applications and have a single ObjectMapper configuration for each?