2

I am struggling with configuring a "custom" ObjectMapper to be used by the Spring Integration DSL transformers. I receive an java.time.Instant json representations that I would like to parse to object properties. i.e:
{"type": "TEST", "source":"TEST", "timestamp":{"epochSecond": 1454503381, "nano": 335000000}}

The message is a kafka message which raises a question: Should I write a custom serializer implementing Kafka encoders/decoders in order to be able to transform the kafka message to the right object or spring-integration have to do this automatically?

fw/dependencies and version:
Spring Boot - 1.3.2.RELEASE
Spring Integration Java Dsl - 1.1.1.RELEASE
FasterXml Jackson - 2.6.5

I've added this Java Configuration to the project following the Jackson documentation: https://github.com/FasterXML/jackson-datatype-jsr310

@Configuration
public class IntegrationConfiguration {

    @Bean
    public JsonObjectMapper<JsonNode, JsonParser> jsonObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        return new Jackson2JsonObjectMapper(mapper);
    }
}

and the following Jackson JSR-310 artefact as well:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.6.5</version>
</dependency>

Based on this post on the Spring blog I don't even have to register the new Java8 time module. https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring#jackson-modules

This is the exception I got:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class java.time.Instant]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: {"type":"TEST","source":"TEST","timestamp":{"epochSecond":1454503381,"nano":335000000}}; line: 1, column: 71] (through reference chain: my.app.MyDto["timestamp"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1106)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:296)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:133)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:520)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:95)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:258)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:125)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2764)
    at org.springframework.integration.support.json.Jackson2JsonObjectMapper.fromJson(Jackson2JsonObjectMapper.java:75)
    at org.springframework.integration.support.json.Jackson2JsonObjectMapper.fromJson(Jackson2JsonObjectMapper.java:44)
    at org.springframework.integration.support.json.AbstractJacksonJsonObjectMapper.fromJson(AbstractJacksonJsonObjectMapper.java:56)
    at org.springframework.integration.json.JsonToObjectTransformer.doTransform(JsonToObjectTransformer.java:78)
    at org.springframework.integration.transformer.AbstractTransformer.transform(AbstractTransformer.java:33)
    ... 74 more

RESOLUTION:
The problem was that I expected that Spring will detect the jackson-datatype-jsr310 archetype and register the JavaTimeModule, but it doesn't which is totally fine. There are two way we can fix this:
1. The accepted answer if we use Spring Boot with Spring Integration as is.
2. If using the Spring Integration Dsl, just keep the IntegrationConfiguration class with the jsonObjectMapper() bean and use it like that:

@Autowired
private JsonObjectMapper jsonObjectMapper;    

return IntegrationFlows
        .from(inboundChannel())
        .transform(Transformers.fromJson(myDto.class, jsonObjectMapper))
        ...
magiccrafter
  • 5,175
  • 1
  • 56
  • 50
  • before changing the "timestamp" to java.time.Instant everything was working like a charm. I will have to test separately the components and isolate the problem... – magiccrafter Feb 03 '16 at 15:31
  • I haven't used `JavaTimeModule` yet, but would you mind to sure us that you really use that `jsonObjectMapper()` for the `JsonToObjectTransformer` definition ? From other side I don't see any tests for such kind of `Instant` representation: https://github.com/FasterXML/jackson-datatype-jsr310/blob/master/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestInstantSerialization.java – Artem Bilan Feb 03 '16 at 16:05
  • I break the problem and got the following results. For some reason the JavaTimeModule is not taken into consideration. Second problem is that I've tested the default json representation of java.time.Instant and it is different (correct: "timestamp":1454514333.855000000). – magiccrafter Feb 03 '16 at 16:26

2 Answers2

3

There is nothing to do with the Spring Boot on the matter to force Spring Integration to use that.

You just need to configure JsonToObjectTransformer with that your jsonObjetMapper():

@Bean
@Transformer(inputChannel="input", outputChannel="output")
JsonToObjectTransformer jsonToObjectTransformer() {
    return new JsonToObjectTransformer(jsonObjectMapper());
}

Although there is no reason to register JsonObjectMapper as a bean.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Nice one. 10x a lot. I tried with defining the jsonToObjectTransformer but I was not aware of the @Transformer annotation. 10x again – magiccrafter Feb 03 '16 at 16:42
  • Well, see more in the Spring Integration Reference Manual: http://docs.spring.io/spring-integration/docs/latest-ga/reference/html/configuration.html#annotations and pay attention to the Spring Integration Java DSL: https://github.com/spring-projects/spring-integration-java-dsl – Artem Bilan Feb 03 '16 at 16:48
0

Have you defined an encoder to your channel adapter?

You should always use an encoder for whichever adapter you're using, inbound channel adapter or message drive channel adapter.

In your case StringEncoder should solve the problem.

<bean id="myEncoder" class="org.springframework.integration.kafka.serializer.common.StringEncoder"/>
  • I think it doesn't matter. He receives the normal JSON String from the Kafka and tries to deserialize it to POJO but fails with the `Instant` ctor issue. – Artem Bilan Feb 03 '16 at 15:48