Using Jackson in Jersey with multiple configured O

2019-04-21 14:43发布

问题:

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?

回答1:

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;
        }
    }
}