REST How to implement polymorphic POST end point: abstract types either need to be mapped to concrete types Exception

房东的猫 提交于 2021-02-19 05:32:02

问题


I work on a SpringBoot application.

I have the following class hierarchy:

public abstract class DlqMessage {
    Long id;
    UUID employeeId;
    EventReason reason;
}

public class ContractUpdateMessage extends DlqMessage {}

public class SingleContractUpdateMessage extends DlqMessage {
    UUID benefitId;
    UUID employerId;
}

So, the classes ContractUpdateMessage and SingleContractUpdateMessage only differ by a couple of fields.

I Have a REST controller that uses POST to create and save a new entity in the DB:

@PostMapping("/messages")
public ResponseEntity<DlqMessage> create(@RequestBody DlqMessage contractUpdateMessage) {
    DlqMesssage dlqEntry = dlqEntityService.save(contractUpdateMessage);
    return ResponseEntity.ok(dlqEntry);
}

Now, I have a test that randomly generates on instance of the one or the other class:

 DlqMessage message = randomOneOf(contractUpdateMessageBuilder().build(), singleContractUpdateMessageBuilder().build());

Then I have a test helper class that uses RestTemplate to send a POST request to the controller:

ResponseEntity<DlqMesssage> response =
                crudRestClient.post("/messages/contract", message, DlqMesssage.class, null);
        return response.getBody();

And invoking the whole things I end up with the following exception:

Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not construct instance of com.orbitbenefits.benefitsselection.server.errorrecovery.entity.DlqMessage: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.orbitbenefits.benefitsselection.server.errorrecovery.entity.DlqMessage: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

And it looks like my request isn't even being sent by the RestTemplate.

BTW, it all works when I split endpoints for each individual subtype:

@PostMapping("/messages/contract")
public ResponseEntity<DlqMessage> create(@RequestBody ContractUpdateMessage contractUpdateMessage) {
    ContractUpdateMessage dlqEntry = (ContractUpdateMessage) dlqEntityService.save(contractUpdateMessage);
    return ResponseEntity.ok(dlqEntry);
}

@PostMapping("/messages/single")
public ResponseEntity<DlqMessage> create(@RequestBody SingleContractUpdateMessage contractUpdateMessage) {
    SingleContractUpdateMessage dlqEntry = (SingleContractUpdateMessage) dlqEntityService.save(contractUpdateMessage);
    return ResponseEntity.ok(dlqEntry);
}

However that looks ugly and is not a "correct" solution.

Basically, I would like to know if it's possible and how to implement a REST end point that takes polymorphic instance as a parameter and how to invoke such an end point?


回答1:


The reason you can't use the abstract type DlqMessage is because you may receive a message like:

{
  "id":300, 
  "employeeId":"d6a00fb2-058c-4bf7-9a0a-7cc538cd85f5"
}

Jackson can't determine which is type of the concrete object that this message is intending to map. The simplest way to handle this is defining a type hint, like:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "messageType")
@JsonSubTypes({
    @JsonSubTypes.Type(value=ContractUpdateMessage.class, name = "ContractUpdateMessage"),
    @JsonSubTypes.Type(value=SingleContractUpdateMessage.class, name = "SingleContractUpdateMessage")
})
public abstract class DlqMessage { ... }

This way, the next time you make an API call you must also include this type hint in your JSON object:

{
  "id":300, 
  "employeeId":"d6a00fb2-058c-4bf7-9a0a-7cc538cd85f5",
  "messageType":"ContractUpdateMessage"
}

This way, Jackson's default object mapper will use the field "messageType" to guess which type of DlqMessage your API is receiving.

Edit: You can find further information here:

https://www.baeldung.com/jackson-annotations




回答2:


The issue is that spring is trying to create an instance of DlqMessage directly from the input payload. Assuming your API accepts JSON input, you'll need to instruct your json parser on how to distinguish between the subtypes based on payload contents.

Here's a link to a similar question that shows an example of how to do so with jackson json. Looks like you'll have to annotate the abstract class with knowledge of the subtypes via @JsonSubTypes as well as provide the distinguishing fields via @JsonTypeInfo:

https://stackoverflow.com/a/27183383/1563240




回答3:


you can you jackson mapping to map the classes. Add a type property in the class.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "type")
@JsonSubTypes({@JsonSubTypes.Type(value = ContractUpdateMessage.class, name = "ContractUpdateMessage"),             
               @JsonSubTypes.Type(value = SingleContractUpdateMessage.class, name = "SingleContractUpdateMessage")})
public abstract class DlqMessage {


来源:https://stackoverflow.com/questions/57626808/rest-how-to-implement-polymorphic-post-end-point-abstract-types-either-need-to

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