Bob Kuhar
Bob Kuhar

Reputation: 11130

Jackson 2.1.2 Polymorphic Deserialization throws JsonMappingException. Why?

Jackson's Polymorphic Serialize/Deserialize capability is really cool, or would be if I could figure out how to apply it to my problem at hand. There is a pretty good article at http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html that I have been unable to adapt to my simplified problem.

In a nutshell, I am able to get Jackson 2.1.2 to Serialize a class hierarchy into JSON string with type information. I am unable, however, to get Jackson 2.1.2 to Deserialize that JSON String back into my class hierarchy. Below is a Unit Test that exposes this issue.

The class hierarchy is simple enough; Base Class with just two direct Subclasses. Further, the JSON output appears to respect my Jackson @JsonTypeInfo and produces a believable string from mapper.writeValueAsString

{"type":"dog","name":"King","breed":"Collie"}

But my call to mapper.readValue( jsonOfKing, Animal.class ) stacktraces with...

FAILED: testJacksonSerializeDeserialize
com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class org.rekdev.fasterjacksonwtf.PolymorphismTests$Dog]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: java.io.StringReader@32b3a5a0; line: 1, column: 14]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObjectUsingNonDefault(BeanDeserializer.java:400)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:289)
....

Here's my unit test.

import org.testng.annotations.*;
import static org.testng.Assert.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.databind.*;

public class PolymorphismTests {
    @Test
    public void testJacksonSerializeDeserialize() throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        Animal king = new Dog();
        king.name = "King";
        ( (Dog) king ).breed = "Collie";
        String jsonOfKing = mapper.writeValueAsString( king );
        // JsonMappingException right here!
        Animal actualKing = mapper.readValue( jsonOfKing, Animal.class );
        assertEquals( king, actualKing );

    }

    @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type" )
    @JsonSubTypes( { @Type( value = Cat.class, name = "cat" ), @Type( value = Dog.class, name = "dog" ) } )
    abstract class Animal {
        public String name;

        @Override
        public abstract boolean equals( Object obj );

        @Override
        public abstract int hashCode();
    }

    class Dog extends Animal {
        public String breed;

        @Override
        public boolean equals( Object obj ) {
            if ( this == obj ) {
                return true;
            }
            if ( obj == null ) {
                return false;
            }
            if ( getClass() != obj.getClass() ) {
                return false;
            }
            final Dog that = (Dog) obj;
            boolean equals = name.equals( that.name ) && breed.equals( that.breed );
            return equals;
        }

        @Override
        public int hashCode() {
            int hashCode = name.hashCode() + breed.hashCode();
            return hashCode;
        }
    }

    class Cat extends Animal {
        public String favoriteToy;

        @Override
        public boolean equals( Object obj ) {
            if ( this == obj ) {
                return true;
            }
            if ( obj == null ) {
                return false;
            }
            if ( getClass() != obj.getClass() ) {
                return false;
            }
            final Cat that = (Cat) obj;
            boolean equals = name.equals( that.name ) && favoriteToy.equals( that.favoriteToy );
            return equals;
        }

        @Override
        public int hashCode() {
            int hashCode = name.hashCode() + favoriteToy.hashCode();
            return hashCode;
        }
    }
}

Why won't the ObjectMapper allow me to readValue process the JSON produced by the ObjectMapper.writeValue()?

Upvotes: 2

Views: 1397

Answers (1)

StaxMan
StaxMan

Reputation: 116512

Make your inner classes static, like:

static class Dog extends Animal { ... }

otherwise things will not work (since non-static inner classes require so-called "implicit this" argument to refer to an instance of enclosing class).

Upvotes: 2

Related Questions