3

Environment:

I use spring boot version 2.3.3.RELEASE


Problem

I register custom deserializers with @JsonComponent annotation.

In some modules and tests I @Autowire jacksons ObjectMapper.

Problem 1: I cannot @Autowire ObjectMapper in @JsonComponent or the @JsonComponent will be ignored completely (without error message)

Problem 2: I want to have the same instance of ObjectMapper in @JsonComponent (or at least OM with same configuration) that I can obtain by Autowire in other Components


Example

Since I don't want custom deserialization for some fields I use the ObjectMapper to use convertValue() in my custom deserializers.

I noticed, that I cannot @Autowire the ObjectMapper in any of my Deserializers or the Configuration will not be picked up for other ObjectMappers that are autowired in my Application (So the Deserializers are not registered correctly)

I'm obtaining the ObjectMapper in the Deserializer with (ObjectMapper) jsonParser.getCodec();

But this ObjectMapper is not configured correctly (meaning e.g. annotations on class/methods are ignored).

I want to deserialize my object as follows:

@Test
void testDeserializeFoo() throws IOException {
    ObjectMapper om = new ObjectMapper(); // this is obtained by jsonParser in deserializer but it behaves the same, since the ObjectMapper lacks the configuration
    InputStream is = getClass().getResourceAsStream("/json/foo.json");
    JsonNode fooNode = om.readTree(is);
    Foo foo =  om.convertValue(fooNode, Foo.class);
    log.info("Foo: " + foo);
}

The constructor of Foo is annotated with @JsonCreator.

The Problem is, that this annotation is ignored by the ObjectMapper I get from the parser in the custom Deserializer. This results in the following error message:

Invalid type definition for type Foo: Argument #0 has no property name, is not Injectable: can not use as Creator [constructor for Foo, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]

Is it possible to have the same instance of ObjectMapper in my custom Deserializer that is used in other Components when Autowired?


Edit

Possible Solution / Test:

I was testing my deserializer like this:

private JsonParser getJsonParser(String resource) throws IOException {
        InputStream src = getInputStream(resource);
        return objectMapper.createParser(src);
}
    
@Test
void testDeserializeFoo() throws IOException {
    JsonParser parser = getJsonParser("/json/foo.json");
    Foo foo = deserializer.deserialize(parser, null);
    System.out.println(foo);
}

The problem was that the objectMapper in getParser() method was a new instance all the time. Now I use the autowired objectMapper. But how does this have any effect?


Conclusion:

I want to use the same instance of ObjectMapper in my JsonComponents (custom deserializers) as the Autowired ObjectMapper (which is provided by spring in other Components), or at least an ObjectMapper with the same configuration.

With configuration I mean: Enabled Jackson Features / Annotation Configurations on classes, methods, fields / registered modules and JsonComponents

Can someone please explain:

  • how and when ObjectMapper is configured in Spring (when are Annotation configurations (JsonCreator, JsonProperty), Feature configurations (like accepting upper case) in application.yml, Custom JsonComponents etc registered on an ObjectMapper
  • what is the lifecycle of this objectMapper instance?
  • What is the lifecycle of a ContextConfiguration, DeserializationConfiguration?
  • How does Spring and Jackson context relate to each other?

I looked for related topics, other answers related where:

  • Spring and Jackson context are not the same (but why, what does this mean?)
  • You need a HandlerInstantiator to Autowire (this is already default in my spring version I assume, also this does not explain why my custom deserializers are not picked up without any error messages when autowiring ObjectMapper in one of the deserializers)
Manuel Waltschek
  • 515
  • 1
  • 6
  • 23
  • 1
    I read your question about 3 times and still have no clue on what you are asking or trying to achieve. Looks like you want to register something with Jackson which will also use jackson aka a circulair dependency. – M. Deinum Oct 08 '20 at 12:40
  • Okay, I will try to make this more clear. I'll edit it later. Although the questions on the bottom should be somehow clear, independently of the problem above. – Manuel Waltschek Oct 08 '20 at 12:46
  • 1
    @ManuelWaltschek more clear, and more *focused* please. It looks like you're trying to ask several questions in one post. – Giorgi Tsiklauri Oct 15 '20 at 12:21
  • 1
    Have you tried to create a bean which returns the instance of configured `ObjectMapper`? – LHCHIN Oct 16 '20 at 05:47
  • @LHCHIN yes, as svr stated in the answer this still leads to unresolved circular dependencies / references. – Manuel Waltschek Oct 19 '20 at 07:52

1 Answers1

1

In general you can use Jackson2ObjectMapperBuilder and build() to generate new object mapper instance with all the recent settings applied. The default ObjectMapper is provided by JackonsObjectMapperConfiguration in JackonAutoConfiguration. All jackson2 object mapper builder instances are created by JacksonObjectMapperBuilderConfiguration in JacksonAutoConfiguration. Details on configuring object mapper can be found here

You can further customize jackson2 object mapper builder using Jackson2ObjectMapperBuilderCustomizerConfiguration if you don't like the standard defaults in JacksonAutoConfiguration

Checkout this answer - it will answer your other questions

Coming to issue - Had the same issue when using jackson2objectbuilder/build or autowiring object mapper for ser/deser annotated with JsonComponent annotation. All these annotations are scanned by JsonComponentModule bean defined in JacksonAutoConfiguration and are registered with default object mapper and all the instances created by jackson2 object mapper builder. So it is not possible to use the not built object mapper and register the ser/deser at the same time. You are right the exception ( beancurrentlyincreationexception ) is suppressed by spring and json component module is not registered with message unresolvale circular reference for object mapper.

I didn't find any solution other than creating a new object mapper outside of spring managed.

s7vr
  • 73,656
  • 11
  • 106
  • 127
  • Thank you, this was what I was looking for! So I can create a Bean in Configuration of Type Jackson2ObjectMapperBuilderCustomizerConfiguration to add additional configuration to the autoconfigured instances? – Manuel Waltschek Oct 17 '20 at 10:24
  • "I didn't find any solution other than creating a new object mapper outside of spring managed." So how would I do this? Using Jackson2ObjectMapperBuilder and build() in the Deserializer, or can I do this in a singleton @Component (static instance)? – Manuel Waltschek Oct 17 '20 at 10:28
  • You are welcome - Creating your own Jackson2ObjectMapperBuilderCustomizerConfiguration bean will allow to further configure the jacksonObjectMapper unbuilt instance. You will still not be able to build instance as JacksonAutoConfiguration will scan the JsonComponentModule - so you will end up with circular reference. I was able to create static class ObjectMapper variable without any customization for my need. – s7vr Oct 18 '20 at 16:05