erp
erp

Reputation: 3024

Jackson deserialization on multiple types

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!

Upvotes: 26

Views: 38520

Answers (1)

carcaret
carcaret

Reputation: 3358

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).

Upvotes: 34

Related Questions