javajsonjackson

Configure Jackson default deserialization subtype using `JsonMapper.Builder`


Let's say there is an existing abstract subclass that I want Jackson to always deserialize as a certain other class, but I didn't create the parent class so I can't add an annotation to the parent class. For example let's say that the requested type is java.lang.Number, but I always want Jackson to deserialize the value as a java.math.BigDecimal.

My actual use case involves an interface I always want to have deserialized as a concrete class I have created. If there is a com.example.thirdparty.Person interface someone has created, when some deserialization scenario arises for Person I want Jackson to deserialize to a com.example.mycustom.Neighbor record that I have created which implements Person.

I am using a Jackson JsonMapper.Builder to create an ObjectMapper. How can I tell the JsonMapper.Builder to do the following?

I assume what I want is similar to the effects of @JsonDeserialize(as=ValueImpl.class), but I want to configure this on an ObjectMapper level using JsonMapper.Builder for java.lang.Number and com.example.thirdparty.Person regardless of the context; i.e. regardless of which classes have those types as fields. (In my use case these requested types won't even be fields anyway, but that's another story.)


Solution

  • Using AbstractTypeResolver

    You can use AbstractTypeResolver to add rules on what desired class to instantiate on deserialization.

    Let's set a rule to always deserialize Number as BigDecimal. You can create the ObjectMapper like this:

    var builder = JsonMapper.builder();
    
    var resolver = new SimpleAbstractTypeResolver();
    resolver.addMapping(Number.class, BigDecimal.class);
    resolver.addMapping(...);
    resolver.addMapping(...);
    
    var module = new SimpleModule();
    module.setAbstractTypes(resolver);
    
    builder.addModule(module);
    
    return builder.build();
    

    Using Module only

    If you don't have a need to add an additional layer of abstraction, simply add type mapping on the Module itself the following way:

    var builder = JsonMapper.builder();
    
    var module = new SimpleModule();
    module.addAbstractTypeMapping(Number.class, BigDecimal.class);
    
    builder.addModule(module);
    
    return builder.build();
    

    Test

    @Test
    void shouldDeserializeAsBigInteger() throws JsonProcessingException {
      Integer a = 2;
    
      var serialized = objectMapper.writeValueAsString(a);
      var deserialized = objectMapper.readValue(serialized, Number.class);
    
      assertThat(deserialized).isInstanceOf(BigDecimal.class);
    }