Jackson deserialize Scala Future - java

I'm trying to figure how to explain Jersey and Jackson how to deserialize a Future that I pass as byte[].
I create my own ContextResolver
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private static ObjectMapper mapper = null;
public ObjectMapperContextResolver() {
mapper = ObjectMapperFactory.getObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
And the implementation of the ObjectMapper
public static ObjectMapper getObjectMapper() {
ObjectMapper defaultObjectMapper = new ObjectMapper();
SimpleModule futureModule = new SimpleModule("FutureModule");
futureModule.<Future>addDeserializer(Future.class, new FutureDeserializer<String>());
defaultObjectMapper.registerModule(futureModule);
return defaultObjectMapper;
}
And then finally in the implementation of my FutureDeserializer
public class FutureDeserializer<T> extends StdDeserializer<Future<T>>{
public FutureDeserializer() {
super(Future.class);
}
#Override
public Future<T> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectMapper mapper=(ObjectMapper)jp.getCodec();
//TODO: Breakpoint never stop here
return null;
}
}
Then I register in my ResourceConfig before start the JerseyTest
ResourceConfig rc = new ResourceConfig();
rc.register(SpringLifecycleListener.class);
rc.register(RequestContextFilter.class);
rc.register(new JacksonFeature());
rc.register(new ObjectMapperContextResolver());
But when I run the test the ObjectMapperContextResolver is invoked and the mapper returned to Jersey, but he never use the FutureDeserializer.
Any idea what I´m doing wrong?

Related

Jax-rs alter serialization response based on request with jackson

Currently facing an issue where I would like to alter the response of jax-rs resources based on some information passed into the response. The change that need to be made is the format of some of the json. Currently I am registering an ObjectMapper using jax #Provider and ContextResolver. However the getContext() method is only invoked a single time for each resource class. It is not possible to alter this based on every request.
Is it possible to inject or access the ObjectMapper in a ContainerResponseFilter?
#Provider
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
#Context
private HttpServletRequest httpRequest;
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer("some data format"));
mapper.registerModule(module);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return getMapperBasedOnRequest();
}
private ObjectMapper getMapperBasedOnRequest() {
if (true) {
return mapper;
} else {
return //something else;
}
}
private boolean containsLegacyHeader() {
return //get some information from the request headers or body
}
}
I decided to solve this issue by using a Filter which would be invoked based on a parameter provided in the request, in this case a header.
public class DemoFilter implements ContainerResponseFilter {
private final String UTC = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
private final String OTHER = "yyyy-MM-dd";
public static final String DATE_UTC = "utc";
private String getMapperBasedOnRequest(ContainerRequestContext requestContext) {
if (checkHeader(requestContext)) {
return OTHER;
} else {
return UTC;
}
}
private boolean checkHeader(ContainerRequestContext requestContext) {
return //check header
}
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
ObjectWriterInjector.set(new DateMod(getMapperBasedOnRequest(requestContext)));
}
public static class DateMod extends ObjectWriterModifier {
private String df;
public DateMod(String df) {
this.df = df;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpointConfigBase, MultivaluedMap<String, Object> multivaluedMap, Object o, ObjectWriter objectWriter, JsonGenerator jsonGenerator) throws IOException {
return objectWriter.with(new SimpleDateFormat(dateFormat));
}
}
}

Jackson deserializer not failing on unknown properties or missing values

When I'm trying to deserialize Object from a String and this String does not contain certain fields or has fields that are not in my Object, Jackson serializer is completely okay with that and just creates my Object with null/Optional.empty() fields, also ignoring unkown properties. I tried to set reader with feature FAIL_ON_UNKNOWN_PROPERTIES but to no success. I have fairly simple Jackson configuration, not much besides adding support for Java 8 and java.time.
Edit:
public final ObjectReader reader;
public final ObjectWriter writer;
private JsonMapperTestInstance() {
ObjectMapper mapper = new JacksonConfiguration().objectMapper();
reader = mapper.reader();
writer = mapper.writer().withFeatures(SerializationFeature.INDENT_OUTPUT);
}
public <T> T deserialize(Class<T> actual, String serialized) throws IOException {
return reader.forType(actual).readValue(serialized);
}
JacksonConfiguration:
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
registerModules(mapper);
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
return mapper;
}
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8Module() {
return new Jdk8Module().configureAbsentsAsNulls(true);
}
private void registerModules(ObjectMapper mapper) {
mapper.registerModule(jdk8Module());
mapper.registerModule(javaTimeModule());
}
#Primary
#Bean
public ObjectWriter writer(ObjectMapper mapper) {
return mapper.writer();
}
#Primary
#Bean
public ObjectReader reader(ObjectMapper mapper) {
return mapper.reader();
}
I have determined that annotation #JasonUnwrapped is causing this behaviour. Without it Jackson throws expection on property "very_wrong_field", which previously was silently ignoring.

How do I get a reference to the Jackson Object Mapper in a jersey2 / hk2 application

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?
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.
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)

How can I control #Provider in JAX-RS runtime

I'm using a customised ContextResolver with my JAX-RS application.
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Provider
public class ObjectMapperContextResolver
implements ContextResolver<ObjectMapper> {
public ObjectMapperContextResolver() {
super();
// I want to control this
objectMapper = new ObjectMapper()
.configure(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME, true);
objectMapper.registerModule(new JaxbAnnotationModule());
}
#Override
public ObjectMapper getContext(final Class<?> type) {
return objectMapper;
}
private final ObjectMapper objectMapper;
}
As you can see, there is an option(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME) customised.
How can I turn off that option on demand in JAX-RS resources?
#Path("/items")
public class ItemsResource {
#GET
#Produces({APPLICATION_JSON, APPLICATION_XML})
public Response read(#QueryParam("wrap") final boolean wrap) {
if (wrap) {
// turn off MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME
// and then return response of [Items]
} else {
// turn on MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME
// and then return response of [List<Item>]
}
}
}
Is there any way to be injected with the provider?

Jackson ObjectMapper using custom Serializers and Deserializers

I've got a class that configures a Jackson ObjectMapper. It adds in some custom serializers and deserializers for my object types as follows:
public class JsonMapperFactory {
public static ObjectMapper createObjectMapper() {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
addCustomDeserializersTo(module);
addCustomSerializersTo(module);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
private static void addCustomSerializersTo(final SimpleModule module) {
module.addSerializer(DateTime.class, new DateTimeSerializer());
}
private static void addCustomDeserializersTo(final SimpleModule objectMapper) {
objectMapper.addDeserializer(DateTime.class, new DateTimeDeserializer());
}
}
I've tested my customer serializers within their own test classes, so in my test of this JsonMapperFactory class, I'm trying to simply check that the ObjectMapper created has the expected serializers (or deserializers) This could be achieve by introspecting the ObjectMapper, but it doesn't seem to have any mechanisms to do this.
Does anyone know of a nice way to test that?
For deserializers, I have the following:
private void assertThatObjectMapperUsesCorrectDeserializer(final Class<?> typeClazz, final Class<?> deserializerClazz) throws JsonMappingException {
final DeserializationConfig deserializationConfig = this.objectMapper.getDeserializationConfig();
final JsonDeserializer<Object> deserializer = this.objectMapper.getDeserializerProvider().findTypedValueDeserializer(deserializationConfig, javaTypeFor(typeClazz), null);
assertThat(deserializer, is(instanceOf(deserializerClazz)));
}
private JavaType javaTypeFor(final Class<?> clazz) {
return TypeFactory.type(clazz); //deprecated method :(
}
Which is quite verbose and uses deprecated methods.
I'm yet to find a way to do a similar test for the serializers. So I've currently resorted to serializing an object and check it serializes correctly (essentially duplicating the serializer test)
Any ideas are very welcome.
From the answers & comments provided here, I recently redesigned the class to use builders for both the Module and the ObjectMapper. This allowed me to provide mocks and check that the correct (de)serializers were added to the module and then the module is registered to the object mapper as expected.
Object Mapper Builder:
public class ObjectMapperBuilder {
ObjectMapper mapper;
public ObjectMapperBuilder configure(final ObjectMapper mapper) {
this.mapper = mapper;
return this;
}
public ObjectMapperBuilder withModule(final Module module) {
this.mapper.registerModule(module);
return this;
}
public ObjectMapper build() {
return this.mapper;
}
}
Module Builder:
public class SimpleModuleBuilder {
SimpleModule module;
public SimpleModuleBuilder configure(final SimpleModule module) {
this.module = module;
return this;
}
public <X> SimpleModuleBuilder withSerializer(final Class<X> clazz, final JsonSerializer<X> serializer) {
this.module.addSerializer(clazz, serializer);
return this;
}
public <X> SimpleModuleBuilder withDeserializer(final Class<X> clazz, final JsonDeserializer<X> deserializer) {
this.module.addDeserializer(clazz, deserializer);
return this;
}
public SimpleModule build() {
return this.module;
}
}
And finally, the new JsonMapperFactory:
public class JsonMapperFactory {
public static ObjectMapper configureObjectMapper(final ObjectMapper mapper, final SimpleModule module) {
final SimpleModuleBuilder modulebuilder = new SimpleModuleBuilder();
final SimpleModule configuredModule = modulebuilder.configure(module)
.withSerializer(DateTime.class, new DateTimeSerializer())
.withDeserializer(DateTime.class, new DateTimeDeserializer())
.build();
final ObjectMapperBuilder objectMapperBuilder = new ObjectMapperBuilder();
return objectMapperBuilder.configure(mapper).withModule(configuredModule).build();
}
}
The factory method is still used within Spring configuration, but the configuration now instantiates the blank Module and ObjectMapper before providing them to the factory methods that then configure them.
If JsonDeserializer (and DateTimeDeserializer too) was an interface, you could easily "JMock" it, pass mocked instance to JsonMapperFactory#createObjectMapper and then expect exactly 1 invocation of your custom "serialize" method; e.g.
DateTimeSerializer serializer = context.mock(DateTimeSerializer.class);
DateTimeDeserializer serializer = context.mock(DateTimeDeserializer.class);
ObjectMapper mapper = JacksonMapperFactory.createObjectMapper(deserializer, serializer);
exactly(1).of(jsonDeserializer).serialize(myDateTime,
with(any(JsonGenerator.class),
with(any(SerializerProvider.class)))
Being a concrete class, you can instead define a new (test-scoped) De/Serializer that extends your custom DateTime(De)serializer, and simply count invocation on that:
private static class DateTimeDeserializerWithCounter extends DateTimeDeserializer {
public int counter = 0;
#Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
counter++;
return super.deserialize(jsonParser, deserializationContext);
}
}
#Test
public void usageTest(){
//init mapper with the above DateTimeDeserializerWithCounter - see below
mapper.readValue("...", DateTime.class);
Assert.assertEquals(1, deserializer.counter);
}
Below a snapshot of a more "test-oriented" Factory:
//package visibility, to allow passing different De/Serializers while testing
static ObjectMapper createObjectMapper(JsonDeserializer deserializer, JsonSerializer serializer) {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
module.addDeserializer(DateTime.class, deserializer);
module.addSerializer(DateTime.class, serializer);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
//production method: no-args, as in the original version
public static ObjectMapper createObjectMapper() {
return createObjectMapper(new DateTimeDeserializer(), new DateTimeSerializer());
}
Hope that helps.

Categories

Resources