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