Question

I have some classes like so:

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonSubTypes(
              {
                  @JsonSubTypes.Type(value = LionCage.class, name = "LION"),
                  @JsonSubTypes.Type(value = TigerCage.class, name = "TIGER"),
              }
             )
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
public abstract class Cage<ANIMAL> {

  private AnimalType type;
  private ANIMAL animal;

}

public enum AnimalType {

    LION, TIGER

}

public LionCage extends Cage<Lion>{

    public LionCage(){
        super(AnimalType.LION);
    }
}

public Lion {

 private int maneLength;

}

public class Zoo {

   private List<Cage<?>> cages = new LinkedList<Cage<?>>();

}

Now, if I try to do the following:

String json = "{cages: [{\"type\": \"LION\", \"animal\": {\"maneLength\" : 10}}]}";

ObjectMapper om = new ObjectMapper();

Cage cage = om.readValue(json, Zoo.class);

The first value in cages should be of type LionCage and it is.

The problem is that cages.get(0).getAnimal() returns a LinkedHashMap. How can I make it return the correct Animal subclass?

I am using Jackson 1.

Thanks!

Was it helpful?

Solution

For your List problem, this is the one time where you want to use a raw type. Declare the Cage type argument as raw, instead of parameterized with a wildcard.

public class Zoo {

    private List<Cage> cages = new LinkedList<Cage>();
    // ... getters and setters similarly
}

With the wildcard, you are telling Jackson that it doesn't matter, so it uses its default, LinkedHashMap. If you remove the wildcard and use a raw type, it will need to look for hints elsewhere. In this case, it will look at the actual Cage implementation it uses.


Your JSON should look like this

String json = "{\"type\": \"LION\", \"animal\": {\"maneLength\" : 10}}";

Jackson will be able to determine that the animal is a Lion.

Through the type it will know that it is a LionCage and through the class definition of

public LionCage extends Cage<Lion>{

it will know that any reference to the type variable Animal should actually be a Lion.

I'm on Jackson 2.

But it works with Jackson 1 as well. Code below

import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonSubTypes;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.map.ObjectMapper;

public class Example {

    public static void main(String[] args) throws Exception {
        String json = "{\"type\": \"LION\", \"animal\": {\"maneLength\" : 10}}";

        ObjectMapper om = new ObjectMapper();

        Cage cage = om.readValue(json, Cage.class);
        System.out.println(cage.getClass());
        System.out.println(cage.getAnimal());

        System.out.println(((Lion) cage.getAnimal()).getManeLength());
    }
}

class Lion {

    private int maneLength;

    public int getManeLength() {
        return maneLength;
    }

    public void setManeLength(int maneLength) {
        this.maneLength = maneLength;
    }
}

class LionCage extends Cage<Lion> {

    public LionCage() {
        super(AnimalType.LION);
    }
}

class Tiger {
}

class TigerCage extends Cage<Tiger> {

    public TigerCage() {
        super(AnimalType.TIGER);
    }
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonSubTypes({ @JsonSubTypes.Type(value = LionCage.class, name = "LION"),
        @JsonSubTypes.Type(value = TigerCage.class, name = "TIGER"), })
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
abstract class Cage<Animal> {

    public Cage(AnimalType type) {
        this.setType(type);
    }

    public AnimalType getType() {
        return type;
    }

    public void setType(AnimalType type) {
        this.type = type;
    }

    public Animal getAnimal() {
        return animal;
    }

    public void setAnimal(Animal animal) {
        this.animal = animal;
    }

    private AnimalType type;
    private Animal animal;
}

enum AnimalType {
    LION, TIGER;
}

prints

class com.spring.LionCage
com.spring.Lion@15d16efc
10

OTHER TIPS

I think you should simplify your solution by removing unnecessary types and by using JsonTypeInfo.As.EXTERNAL_PROPERTY instead of JsonTypeInfo.As.PROPERTY. With JsonTypeInfo.As.EXTERNAL_PROPERTY you do not have to create Cage subclasses. And you do not have to create AnimalType at all. You POJO classes could look like this:

@JsonIgnoreProperties(ignoreUnknown = true)
class Cage<ANIMAL> {

    @JsonSubTypes({
        @JsonSubTypes.Type(value = Lion.class, name = "LION"),
        @JsonSubTypes.Type(value = Tiger.class, name = "TIGER")
    })
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
    private ANIMAL animal;

    public ANIMAL getAnimal() {
        return animal;
    }

    public void setAnimal(ANIMAL animal) {
        this.animal = animal;
    }

    @Override
    public String toString() {
        return "Cage [animal=" + animal + "]";
    }
}

class Lion {

    private int maneLength;

    public int getManeLength() {
        return maneLength;
    }

    public void setManeLength(int maneLength) {
        this.maneLength = maneLength;
    }

    @Override
    public String toString() {
        return "Lion [maneLength=" + maneLength + "]";
    }
}

class Tiger {

    private int length;

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    @Override
    public String toString() {
        return "Tiger [maneLength=" + length + "]";
    }
}

Simple usage:

String jsonLion = "{\"type\": \"LION\", \"animal\": {\"maneLength\" : 10}}";
String jsonTiger = "{\"type\": \"TIGER\", \"animal\": {\"length\" : 11}}";

ObjectMapper om = new ObjectMapper();

System.out.println(om.readValue(jsonLion, Cage.class));
System.out.println(om.readValue(jsonTiger, Cage.class));

Above code prints:

Cage [animal=Lion [maneLength=10]]
Cage [animal=Tiger [maneLength=11]]

I did not test my solution with Jackson 1.X but I hope it will work for you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top