I have an abstract class called Instance
and then two implementations of that, UserInstance
and HardwareInstance
. The issue I am having is that when I call the rest endpoint for a @POST
into the database, I ideally wanted it to be like .../rest/soexample/instance/create
where the instance is passed to the REST endpoint. If Instance
wasn't abstract with more than one implementation it would be fine, but since I have 2 I am getting a Jackson.databind
error.
" problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information"
After looking up a solution to this I found a SO answer that said I could use something like:
@JsonDeserialize(as=UserInstance.class)
But it seem's like that isonly useful if there is one implementation of the abstract class. Assuming I can't call it twice since there would be no way for it to decide which type of instance it would be.
So I am wondering what is the best way to handle this situation? Should I create different endpoints? Like:
.../rest/soexample/userinstance/create
& .../rest/soexample/hardwareinstance/create
I am not too sure as I am a noobie @ REST related things, though actively trying to learn. Thanks!
This is safe as ObjectMapper is thread-safe after configuration. Caveat: As @StaxMan points out in the answer comments, this does NOT guarantee against further (re)configuration on the returned singleton.
Jackson is a powerful and efficient Java library that handles the serialization and deserialization of Java objects and their JSON representations. It's one of the most widely used libraries for this task, and runs under the hood of many other frameworks.
A polymorphic deserialization allows a JSON payload to be deserialized into one of the known gadget classes that are documented in SubTypeValidator. java in jackson-databind in GitHub. The deserialized object is assigned to a generic base class in your object model, such as java. lang.
Jackson uses default (no argument) constructor to create object and then sets value using setters. so you only need @NoArgsConstructor and @Setter.
Here is what I did in your same case:
@JsonDeserialize(using = InstanceDeserializer.class) public abstract class Instance { //.. methods } @JsonDeserialize(as = UserInstance.class) public class UserInstance extends Instance { //.. methods } @JsonDeserialize(as = HardwareInstance.class) public class HardwareInstance extends Instance { //.. methods } public class InstanceDeserializer extends JsonDeserializer<Instance> { @Override public Instance deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ObjectMapper mapper = (ObjectMapper) jp.getCodec(); ObjectNode root = (ObjectNode) mapper.readTree(jp); Class<? extends Instance> instanceClass = null; if(checkConditionsForUserInstance()) { instanceClass = UserInstance.class; } else { instanceClass = HardwareInstance.class; } if (instanceClass == null){ return null; } return mapper.readValue(root, instanceClass ); } }
You annotate Instance
with @JsonDeserialize(using = InstanceDeserializer.class)
to indicate the class to be used to deserialize the abstract class. You need then to indicate that each child class will be deserialized as
themselves, otherwise they will use the parent class deserializer and you will get a StackOverflowError
.
Finally, inside the InstanceDeserializer
you put the logic to deserialize into one or another child class (checkConditionsForUserInstance()
for example).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With