How to define entity inheritance in a SpringBoot openapi type definition when POST-ing a @RequestBody
containing an object map of polymorphic entities?
Shouldn't it be sufficient to set anyOf = { PolarParrot.class, NorwegianParrot.class}, discriminatorProperty = "parrotType"
in order to type hint which concrete Parrot
instance must be used to resolve the RequestBody
?
import java.lang.Override;
import java.net.URI;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
@RestController
public class PolyParrot {
@PostMapping(value = {"/create"}, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> create(@Valid @RequestBody Flock flock) {
return ResponseEntity.created(URI.create("/flock/42")).build();
}
}
@Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE)
class Flock {
@Schema
Map<String, Parrot> birds;
public Map<String, Parrot> getBirds() {
return birds;
}
public void setBirds(Map<String, Parrot> birds) {
this.birds = birds;
}
}
@Schema(anyOf = {
PolarParrot.class,
NorwegianParrot.class,
}, discriminatorProperty = "parrotType")
interface Parrot {
String getParrotType();
String getPlumage();
}
@Schema()
class PolarParrot implements Parrot {
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "polar", example = "polar")
String parrotType;
@Override
public String getParrotType() {
return parrotType;
}
@Override
public String getPlumage() {
return "red";
}
}
@Schema()
class NorwegianParrot implements Parrot {
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "norwegian", example = "norwegian")
String parrotType;
@Override
public String getParrotType() {
return parrotType;
}
@Override
public String getPlumage() {
return "blue";
}
}
According to docs and examples it appears that anyOf
+ discriminatorProperty
should be enough, yet, when POST-ing a valid payload instead of a deserialized flock
object it throws:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `Parrot`
(no Creators, like default constructor, exist):
abstract types either need to be mapped to concrete types,
have custom deserializer, or contain additional type information
Doesn't Parrot
contain enough 'additional type information' already?
Which essential part did I miss here?
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of Parrot
(no Creators, like default constructor, exist):
abstract types either need to be mapped to concrete types,
have custom deserializer, or contain additional type information
The error states that, during deserialization it was unable to resolve the subtype in payload with reference to the abstract type.
Annotate Parrot interface with this and include the key 'type' in the payload like below depending on the subclass accordingly:
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property = "parrotType")
@JsonSubTypes({
@JsonSubTypes.Type(value = PolarParrot.class, name = "polarParrot"),
@JsonSubTypes.Type(value = NorwegianParrot.class, name =
"norwegianParrot")
})
Sample payload:
{
"birds": {
"parrot1": {
"type":"polarParrot",
"parrotType": "polar"
},
"parrot2": {
"type":"norwegianParrot",
"parrotType": "norwegian"
}
}
}