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

Nestor Milyaev :

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?

dliber :

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=141889&siteId=1