How to provide a custom deserializer with Jackson and Spring Boot

怎甘沉沦 提交于 2019-12-18 09:11:21

问题


I have the following three applications:

Project 1 holds

  • Business logic (Spring Cloud Function)
  • An interface IDemoEntity

Project 2

  • AWS-specific handler
  • One implementation of IDemoEntity, with DynamoDB-specific annotations
  • The project is based on Spring Boot

Project 3

  • One implementation of IDemoEntity, with CosmosDB annotation
  • Azure-specific handler

The classes of project 1 look like this:

public interface IDemoEntity {
    String getName();
    void setName(String name);
}

@Component
public class StoreFunction implements Consumer<Message<IDemoEntity>> {

    @Override
    public void accept(Message<IDemoEntity> t) {

        System.out.println("Stored entity " + t.getPayload().getName());
        return;
    }
}

For project 2, the implementation of IDemoEntity looks like this:

@DynamoDBTable(tableName = "DemoEntity")
public class DynamoDemoEntity implements IDemoEntity {

    private String name;
    @Override
    @DynamoDBHashKey
    public String getName() {

        return name;
    }

    @Override
    public void setName(String name) {

        this.name = name;
    }    
}

For project 3, the implementation of IDemoEntity would look similar to DynamoDemoEntity, but with CosmosDB annotations.

The structure might look a bit complicated, but the idea is the following:

  1. Implement business logic and data model one time (in project 1) (leveraging Spring Cloud Function)
  2. Implement just a wrapper project for each platform (I'm starting with AWS Lambda in project 2, but project 3 for Azure would look similar), as well as platform-specific things (like entity implementation, which needs DB-specific annotations)
  3. Compile the platform-specific project (e. g. project 2 for AWS Lambda) with project 1 as dependency

I've tried it, and the setup works basically. However, there is one big problem:

When calling the StoreFunction above, Jackson throws the following exception:

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `de.margul.awstutorials.springcloudfunction.logic.IDemoEntity` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (String)"{"name": "Detlef"}"; line: 1, column: 1]
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1028)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
    at de.margul.awstutorials.springcloudfunction.aws.handler.RestSpringBootApiGatewayRequestHandler.deserializeBody(RestSpringBootApiGatewayRequestHandler.java:57)
    ... 3 more

That makes sense, because Jackson does not know, to which implementation if IDemoEntity it shall deserialize the received JSON.

The easiest way now would be to place a @JsonDeserialize(as = DynamoDemoEntity.class) on IDemoEntity.

However, that would break my complete structure: Project 1 shall have no information, which platform-specific project it is compiled with.

Any ideas, how I could provide a custom deserializer (e. g. as Spring bean), but without making platform-specific modifications in project 1?


回答1:


Firstly, you need to create your custom DynamoDemoEntityDeserializer like below:

class DynamoDemoEntityDeserializer extends JsonDeserializer<DynamoDemoEntity> {
    @Override
    public DynamoDemoEntity deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
       // return DynamoDemoEntity instance;
    }
}

Then you can create bean of com.fasterxml.jackson.databind.Module like below:

@Bean
public Module dynamoDemoEntityDeserializer() {
    SimpleModule module = new SimpleModule();
    module.addDeserializer(IDemoEntity.class, new DynamoDemoEntityDeserializer());
    return module;
}

Any beans of type com.fasterxml.jackson.databind.Module are automatically registered with the auto-configured Jackson2ObjectMapperBuilder and are applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.

Source: howto-customize-the-jackson-objectmapper




回答2:


If you want to deserialize your given object to any entity then you need to write your own custom method for that.

Here is an example that I have used for the same and it works fine for any Entity. Please have a look and try.

@Service
public class ObjectSerializer {
    private static final Logger logger = LoggerFactory.getLogger(ObjectSerializer.class);

    private static ObjectMapper objectMapper;

    @Autowired
    private ObjectSerializer(ObjectMapper objectMapper) {
        ObjectSerializer.objectMapper = objectMapper;
    }

    public static <T> T getObject(Object obj, Class<T> class1) {
        String jsonObj = "";
        T objectDto = null;
        try {
            jsonObj = objectMapper.writeValueAsString(obj);
            objectDto =  objectMapper.readValue(jsonObj, class1);
        }catch (IOException e) {
            logger.error("Exception Occured in ObjectSerializer :    |   {}",e.getMessage());
        }
        return objectDto;
    }

I hope this helps.



来源:https://stackoverflow.com/questions/53480525/how-to-provide-a-custom-deserializer-with-jackson-and-spring-boot

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!