5

Let there be an abstract class A with a property a and three non-abstract subclasses B, C and D. B has no additional property, C contains the property c and D contains both properties c and d.

I would like to subclass StdDeserializer for the abstract class A to be able to decide based on the existence of properties to be deserialized which subclass to choose.

I did that with some Jackson release from Codehaus before and it was working fine using the following implementation:

class AbstractADeserializer extends StdDeserializer<A> {
    AbstractADeserializer () {
        super(A.class);
    }

    @Override
    public A deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        ObjectNode root = (ObjectNode) mapper.readTree(jp);
        Class<? extends A> requestClass = null;

        // root.getFieldNames() is an iterator over all field names
        JsonNode cValue = root.findValue("c");
        JsonNode dValue = root.findValue("d");

        /*
         * Check for existence of required fields and choose the
         * corresponding request class.
         */
        logger.debug(Boolean.toString(c != null));
        logger.debug(Boolean.toString(d != null));

        if(c != null && d == null) {
            logger.debug("Found C");
            requestClass = C.class;
        } else if(c != null && d != null) {
            logger.debug("Found D");
            requestClass = D.class;
        } else {
            logger.debug("Found B");
            requestClass = B.class;
        }

        return mapper.readValue(root, requestClass);
    }
}

This worked fine but after migration to Jackson 2.4 from FasterXML ObjectMapper does not allow ObjectNode as a parameter for it's readValue method.

When I modified the code to use return mapper.readValue(jp, requestClass); I always get

    com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input
     at [Source: org.apache.catalina.connector.CoyoteInputStream@1286ec89; line: 1, column: 559]
        at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
        at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:3095)
        at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3009)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1637)

What alternatives do you see the flexible determine the class of a given input without needing to deserialize the object manually? To me it looks complicated to clone the JsonParser to avoid the exhaustion of it's input source.

Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
Christoph
  • 230
  • 1
  • 3
  • 10
  • Here you go: http://wiki.fasterxml.com/JacksonPolymorphicDeserialization – Robby Cornelissen Aug 26 '14 at 10:01
  • Hello @RobbyCornelissen . Thank you for pointing me to this page. I know it and do not want to include type info properties. – Christoph Aug 26 '14 at 14:01
  • A further reason why I cannot just deserialize the objects myself is the issue that we have a more complex nested object inside one of the non-abstract classes. Therefore deserializing manually would be really messy and is better to be delegated to Jackson. – Christoph Aug 26 '14 at 17:03
  • 1
    I was trying to do the same thing with the class hierarchy (have the deserializer choose the right subclass based on fields) and this method didn't work for me with codehaus 1.9 or with fasterxml 2.4.1. I kept on getting a stackoverflow exception because readValue/treeToValue called the deserializer back. The work around was to use the technique above along with a DeserializerModifier - http://stackoverflow.com/questions/18313323/how-do-i-call-the-default-deserializer-from-a-custom-deserializer-in-jackson. – Mohit Chugh Sep 08 '15 at 06:27
  • 4
    Update: Turns out I had to add, @JsonDeserialize(using = JsonDeserializer.None.class) to the derived classes. This neatly avoids the recursion and once I do that, I can use the technique outlined in the question above. – Mohit Chugh Sep 08 '15 at 08:07

1 Answers1

5

Just ran into similar issues, and for me it was all fixed by replacing mapper.readValue(jp, requestClass) with mapper.treeToValue(root, requestClass)

Dave
  • 1,204
  • 2
  • 14
  • 25
  • Thank you very much for your answer! Bad that I did not see the method myself but I guess i just overlooked it in the mass of methods :) – Christoph Dec 19 '14 at 14:47