Is it possible to use multiple #JsonSubType annotations in a nested fashion?
For example, imagine the following classes:
#Data
#JsonSubTypeInfo(include=As.EXISTING_PROPERTY, property="species", use=Id.NAME, visible=true)
#JsonSubTypes({
#Type(name="Dog", value=Dog.class)
#Type(name="Cat", value=Cat.class)
})
public abstract class Animal {
private String name;
private String species;
}
#Data
#JsonSubTypeInfo(include=As.EXISTING_PROPERTY, property="breed", use=Id.NAME, visible=true)
#JsonSubTypes({
#Type(name="Labrador", value=Labrador.class)
#Type(name="Bulldog", value=Bulldog.class)
})
public abstract class Dog extends Animal {
private String breed;
}
#Data
public class Cat extends Animal {
private boolean lovesCatnip;
}
#Data
public class Labrador extends Dog {
private String color;
}
#Data
public class Bulldog extends Dog {
private String type; // "frenchy", "english", etc..
}
If I use an object mapper, I can successfully map a Bulldog to JSON, however, when trying to read the resulting JSON and read it back in, I get an error like the following:
Can not construct instance of com.example.Dog abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Is it possible to get Jackson to work with such subtyping? Would I need to create a custom deserializer for each subclass?
EDIT:
I've modified the classes above slightly from the original posting. I added a Cat class and had that and Dog extend from Animal.
Here is a sample JSON that can be created using the ObjectMapper::writeValueAsString:
{
"name": null,
"species": "Dog",
"breed": "Bulldog",
"type": "B-Dog"
}
The following works if I use #JsonTypeInfo and a similar set up to yours. Maybe your problem is in your deserialization code, so have a look at this:
public class MyTest {
#Test
public void test() throws IOException {
final Bulldog bulldog = new Bulldog();
bulldog.setBreed("Bulldog");
bulldog.setType("B-Dog");
final ObjectMapper om = new ObjectMapper();
final String json = om.writeValueAsString(bulldog);
final Dog deserialized = om.readValue(json, Dog.class);
assertTrue(deserialized instanceof Bulldog);
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "species", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(name = "Dog", value = Dog.class),
#Type(name = "Cat", value = Cat.class)
})
public static abstract class Animal {
private String name;
private String species;
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "breed", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(name = "Labrador", value = Labrador.class),
#Type(name = "Bulldog", value = Bulldog.class)
})
public static abstract class Dog {
private String breed;
public String getBreed() {
return breed;
}
public void setBreed(final String breed) {
this.breed = breed;
}
}
public static abstract class Cat {
private String name;
}
public static class Labrador extends Dog {
private String color;
public String getColor() {
return color;
}
public void setColor(final String color) {
this.color = color;
}
}
public static class Bulldog extends Dog {
private String type; // "frenchy", "english", etc..
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
}
}
EDITed for the updated question: If you can use the same property (in the following code the hidden property "#class") for the inheritance hierarchy, it works:
#Test
public void test() throws IOException {
final Bulldog bulldog = new Bulldog();
// bulldog.setSpecies("Dog");
// bulldog.setBreed("Bulldog");
bulldog.setType("B-Dog");
final ObjectMapper om = new ObjectMapper();
final String json = om.writeValueAsString(bulldog);
final Animal deserialized = om.readValue(json, Animal.class);
assertTrue(deserialized instanceof Bulldog);
}
#JsonTypeInfo(include = As.PROPERTY, use = Id.CLASS, visible = false)
#JsonSubTypes({
#Type(Dog.class),
#Type(Cat.class)
})
public static abstract class Animal {
}
#JsonTypeInfo(include = As.PROPERTY, use = Id.CLASS, visible = false)
#JsonSubTypes({
#Type(name = "Labrador", value = Labrador.class),
#Type(name = "Bulldog", value = Bulldog.class)
})
public static abstract class Dog
extends Animal {
}
If you want to set the animal type (e.g. to compute species, breed etc.), you could also use this setup:
#Test
public void test() throws IOException {
final Bulldog bulldog = new Bulldog();
bulldog.setAnimalType("Bulldog");
// bulldog.setSpecies("Dog");
// bulldog.setBreed("Bulldog");
bulldog.setType("B-Dog");
final ObjectMapper om = new ObjectMapper();
final String json = om.writeValueAsString(bulldog);
System.out.println(json);
final Animal deserialized = om.readValue(json, Animal.class);
assertTrue(deserialized instanceof Bulldog);
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "animalType", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(Dog.class)
})
public static abstract class Animal {
private String animalType;
public String getAnimalType() {
return animalType;
}
public void setAnimalType(final String animalType) {
this.animalType = animalType;
}
}
#JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "animalType", use = Id.NAME, visible = true)
#JsonSubTypes({
#Type(value = Bulldog.class)
})
public static abstract class Dog
extends Animal {
}
#JsonTypeName("Bulldog")
public static class Bulldog extends Dog {
private String type; // "frenchy", "english", etc..
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
}
I was able to solve this such that the following JSON translates to a Bulldog object:
{
"species": "Dog",
"breed": "Bulldog",
"name": "Sparky",
"type": "English"
}
I used the following code to do this:
ObjectMapper om = new ObjectMapper();
om.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleMissingInstantiator(DeserializationContext ctxt, Class<?> instClass, JsonParser p, String msg) throws IOException {
JsonNode o = p.readValueAsTree();
JsonNode copy = o.deepCopy();
JsonNode species = o.get("species");
if (species != null) {
Class<? extends Animal> clazz;
switch (species.asText()) {
case "Dog":
clazz = Dog.class;
break;
case "Cat":
clazz = Cat.class;
break;
default:
return NOT_HANDLED;
}
JsonParser parser = new TreeTraversingParser(copy, p.getCodec());
parser.nextToken(); // without this an error is thrown about missing "breed" type
return ctxt.readValue(parser, clazz);
}
return NOT_HANDLED;
}
});
I believe there's probably a better way to find the typed class (I noticed that the there is a cache in one of the inputs to the handleMissingInstantiator method that contains all of the relevant types, that can probably be used to find the type based on name instead of hardcoding values as I'm doing.
Related
I am using the Jackson for the Deseilization of the JSON. The Deseilization works perfectly for a JSON with CustomerDocument. However, I have a new requirement in which I need to find whether provided JSON has CustomerDocument or just Customer.
I am able to develop the logic for both but the problem is that when I try to merge it won't work for CustomerDocument. I am looking for a solution that would work for both. All I would like to do is build the logic to differentiate the incoming JSON based on customerDocument and single Customer.
Following is the CustomerDocument JSON:
{
"isA": "CustomerDocument",
"customerList": [
{
"isA": "Customer",
"name": "Batman",
"age": "2008"
}
]
}
Customer.class:
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer {
private String isA;
private String name;
private String age;
}
JacksonMain:
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
//Go until the customerList has been reached
while (!jsonParser.getText().equals("customerList")) {
jsonParser.nextToken();
}
jsonParser.nextToken();
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, Customer.class);
System.out.println(event.toString());
}
}
}
The above code works perfectly and produces the following result:
Customer(isA=Customer, name=Batman, age=2008)
Scenario-2
Now user can provide the direct customer object without the customerDocument. Something like this:
{
"isA": "Customer",
"name": "Superman",
"age": "2013"
}
'Customer.class' would remain the same and JacksonMain would be modified to:
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
final JsonNode jsonNode = jsonParser.readValueAsTree();
final String inputType = jsonNode.get("isA").asText();
if (inputType.equalsIgnoreCase("Customer")) {
Object singleCustomer = objectMapper.treeToValue(jsonNode, Customer.class);
System.out.println(singleCustomer.toString());
} else if (inputType.equalsIgnoreCase("CustomerDocument")) {
//Go until the customerList has been reached
while (!jsonParser.getText().equals("customerList")) {
jsonParser.nextToken();
}
jsonParser.nextToken();
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, Customer.class);
System.out.println(event.toString());
}
}
}
}
For a single CUstomer this would produce the following result:
Customer(isA=Customer, name=Superman, age=2013)
For the same code now if I provide the CustomerDocument (the first JSON) then it would not work and fail with error:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because the return value of "com.fasterxml.jackson.core.JsonParser.getText()" is null
at stackover.JacksonMain.main(JacksonMain.java:32)
I know this issue is happening because of the line
final JsonNode jsonNode = jsonParser.readValueAsTree();
Can someone please explain how to make the code work for both the type of JSON customerDocument and just single Customer using Jackson? I just want to differentiate whether incoming JSON is customerDocument or single Customer. Any help would be really appreciated.
I want to use Jackson to make the differentiation between both the input.
It would be great if there is no need to create any additional classes. However, it's fine if there is a need to create an interface to achieve this.
My CustomerList can be very huge so I am reading one by one so it does not make much memory. hence I do not have the CustomerDocument class with List<Customer> rather I am looking over it and mapping one by one.
Well you can use Jackson sub type to de-serialize between Customer and CustomerDocument.
Something like following,
public class Main {
public static void main(String[] args) throws IOException {
String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"}]}";
// String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";
ObjectMapper om = new ObjectMapper();
BaseResponse baseResponse = om.readValue(s, BaseResponse.class);
if (baseResponse instanceof CustomerDocument) {
CustomerDocument cd = (CustomerDocument) baseResponse;
System.out.println("Inside If..");
cd.getCustomerList().forEach(System.out::println);
} else if (baseResponse instanceof Customer) {
System.out.println("Inside Else If..");
Customer cs = (Customer) baseResponse;
System.out.println(cs);;
}
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonSubTypes({
#JsonSubTypes.Type(value = Customer.class, name = "Customer"),
#JsonSubTypes.Type(value = CustomerDocument.class, name = "CustomerDocument")})
interface BaseResponse {}
#Getter
#Setter
#ToString
class Customer implements BaseResponse{
private String isA;
private String name;
private String age;
}
#Getter
#Setter
#ToString
class CustomerDocument implements BaseResponse{
private String isA;
private List<Customer> customerList;
}
PS - Uncomment the string in main method to illustrate the other case.
Update
public class Main {
public static void main(String[] args) throws IOException {
String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"},{\"isA\":\"Customer B\",\"name\":\"Superman\",\"age\":\"2013\"}]}";
// String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";
ObjectMapper om = new ObjectMapper();
JsonNode node = om.readTree(s);
String type = node.get("isA").asText();
if (type.equals("Customer")) {
Customer c = om.readValue(s, Customer.class);
System.out.println(c);
} else if (type.equals("CustomerDocument")) {
JsonNode nextNode = node.path("customerList");
List<Customer> cl = om.convertValue(nextNode, new TypeReference<List<Customer>>() {});
cl.forEach(System.out::println);
}
}
}
#Getter
#Setter
#ToString
class Customer {
private String isA;
private String name;
private String age;
}
Following worked for me based on the above provided the answer:
BaseResponse interface:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonSubTypes({
#JsonSubTypes.Type(value = Customer.class, name = "Customer")})
public interface BaseResponse {
}
Customer class:
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer implements BaseResponse {
private String isA;
private String name;
private String age;
}
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
try {
BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
System.out.println("SINGLE EVENT INPUT");
System.out.println(baseResponse.toString());
} catch (Exception e) {
System.out.println("LIST OF CUSTOMER INPUT");
//Go until the customerList has been reached
while (!jsonParser.getText().equals("customerList")) {
jsonParser.nextToken();
}
jsonParser.nextToken();
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, BaseResponse.class);
System.out.println(event.toString());
}
}
}
}
I need to deserialize abstract objects, but I always get an exception. I have 3 abstract classes in which I need to connect with each other.
I have a classes structure that looks like the following:
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Child child = new Child(15, "child", "v1");
final String json = mapper.writeValueAsString(child);
final BaseClass baseClass = mapper.readValue(json, BaseClass.class);
System.out.println(baseClass);
}
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
visible = true,
property = "version"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = People.class, name = "v1"),
#JsonSubTypes.Type(value = Animal.class, name = "v2"),
})
#JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class BaseClass {
public String version;
public BaseClass(String version) {
this.version = version;
}
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
visible = true,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = Dog.class, name = "dog")
})
public abstract class Animal extends BaseClass {
public String type;
public Animal(String type, String version) {
super(version);
}
}
public class Dog extends Animal {
public String name;
#JsonCreator
public Dog(#JsonProperty("name") String name, #JsonProperty("type") String type, #JsonProperty("version") String version) {
super(type, version);
this.name = name;
}
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
visible = true,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = Child.class, name = "child")
})
public abstract class People extends BaseClass {
public String type;
public People(String type, String version) {
super(version);
this.type = type;
}
}
public class Child extends People {
public int age;
#JsonCreator
public Child(#JsonProperty("id") int age, #JsonProperty("type") String type, #JsonProperty("version") String version) {
super(type, version);
this.age = age;
}
}
When I tried to run this code, I got the following exception:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.test.People` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"type":"child","version":"v1","age":15}"; line: 1, column: 27]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1209)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:274)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:135)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)
at com.test.Main.main(Main.java:30)
What I do wrong? How can I fix this issue?
I have class hierarchy A <- B <- C. I don't want class A to be aware of C, so I intend to use A.type="B" to indicate it to be class B, and then B.type2="C" to indicate it to be class C.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({#JsonSubTypes.Type(value = B.class, name = "B")})
public abstract class A {
private final String type;
public A(String type) {
this.type = type;
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type2")
#JsonSubTypes({#JsonSubTypes.Type(value = C.class, name = "C")})
public class B extends A {
private final String type2;
private final String propertyB;
#JsonCreator
public B(#JsonProperty("type2") String type2,
#JsonProperty("propertyB") String propertyB) {
super("B");
this.type2 = type2;
this.propertyB = propertyB;
}
}
public class C extends B {
private final String propertyC;
#JsonCreator
public C(#JsonProperty("propertyB") String propertyB,
#JsonProperty("propertyC") String propertyC) {
super("C", propertyB);
this.propertyC = propertyC;
}
}
When I read JSON of C to class A, the actual Java object is class B but not C.
#Test
void whenReadCJsonToA_thenObjectIsInstanceOfC() throws JsonProcessingException {
String json = "{\n" +
" \"type\" : \"B\",\n" +
" \"type2\" : \"C\",\n" +
" \"propertyB\" : \"b\",\n" +
" \"propertyC\" : \"c\"\n" +
"}";
A obj = objectMapper.readValue(json, A.class);
assertTrue(obj instanceof B, "obj is not instance of B"); // pass
assertTrue(obj instanceof C, "obj is not instance of C"); // fail
}
One way to make above test pass is writing custom deserializer, but this solution is tedious if the class holds many fields.
Is it possible to make above test pass with a more elegant way? Is my indention to cascade #JsonTypeInfo and #JsonSubTypes completely wrong?
My maven project can be found in Github.
According to this Jackson issue, multiple level inheritance is supported with only one type discliminator property. In my code, keep only property type and remove property type2.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({#JsonSubTypes.Type(value = B.class, name = "B")})
public abstract class A {
private final String type;
public A(String type) {
this.type = type;
}
}
#JsonSubTypes({#JsonSubTypes.Type(value = C.class, name = "C")})
public class B extends A {
private final String propertyB;
#JsonCreator
public B(#JsonProperty("propertyB") String propertyB) {
super("B");
this.propertyB = propertyB;
}
}
public class C extends B {
private final String propertyC;
#JsonCreator
public C(#JsonProperty("propertyB") String propertyB,
#JsonProperty("propertyC") String propertyC) {
super(propertyB);
this.propertyC = propertyC;
}
}
See full code in this commit.
This commit uses enum as type discliminator property type.
I need deserialize JSON to class depends on type of one of the property.
I have the following JSON:
{
"type":"text",
"message": "Hello"
}
and I have the following Enum:
public enum MyEnumType {
TEXT("text"),
DATE("date")
private final String type;
MyEnumType(String type) {
this.type = type;
}
}
and I have the Abstract class shown as the following:
public abstract class MyClass {
public MyEnumType type;
}
and I have some of classes which extends MyClass
public class TextMessage extends MyClass {
public String message;
}
public class DateMessage extends MyClass {
...
}
and I need to write some code looks like the following:
ObjectMapper mapper = new ObjectMapper();
MyClass instance = mapper.readValue(json, MyClass.class);
and have to get instance depends on type property, if type is TEXT to deserialize to TextMessage class otherwise to DateMessage class.
How can I do this? Can you give me some ideas or examples?
You approach is a little bit confuse but maybe you could do something similar to this:
public class Test {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
String json = "{\"type\":\"text\", \"message\": \"Hello\"}";
// create ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();
MainObj mo = objectMapper.readValue(json, MainObj.class);
System.out.println("type: " + mo.getType());
MyClass instance = null;
if (mo.getType().equalsIgnoreCase("text")) {
// Do it for the 'text'
} else {
// Do it for the 'date'
}
}
}
class MainObj {
private String type = "";
private Object message = null;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Object getMessage() {
return message;
}
public void setMessage(Object message) {
this.message = message;
}
}
class MyClass {
public MyClass() {
}
}
is it possible to have a complex inheritance structure serialized and deserialized with jackson? what are the annotations for it? for example if I had the following classes
#Inheritance
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = Dog.class,name = "dog")
#Type(value = Cat.class,name = "cat")
})
public class Animal implements Serializable{
#id
long id;
String name;
}
this of course is the parent class. I know this is correct if cat and dog do not have any inheriting classes. if I want subclasses of dogs what would I need to change in both the animal class and the dog class?
here is the second class just for reference
#JsonTypeName("dog")
public class Dog extends Animal implements Serializable{
//all my props etc here
{
how would I make a retriever class and a yorki class that inherit from both animal and dog that I could cast to either one and have jackson not freak out at me.
Multi-level polymorphic tree should not be a problem with Jackson. Here is an example of the serializing / de-serializing class hierarchy similar to what you have in your question as it stands in the Jackson wiki page.
public class JacksonPolymorphism3 {
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
public abstract static class Animal {
public final String animalField;
#JsonCreator
public Animal(#JsonProperty("animalField") String animalField) {
this.animalField = animalField;
}
}
#JsonTypeName("dog")
public static class Dog extends Animal {
public final String dogField;
public Dog(#JsonProperty("animalField") String animalField,
#JsonProperty("dogField") String dogField) {
super(animalField);
this.dogField = dogField;
}
#Override
public String toString() {
return "Dog{" +
"dogField='" + dogField + '\'' +
'}';
}
}
#JsonTypeName("husky")
public static class Husky extends Dog {
public final String huskyField;
public Husky(#JsonProperty("animalField") String animalField,
#JsonProperty("dogField") String dogField,
#JsonProperty("huskyField") String huskyField) {
super(animalField, dogField);
this.huskyField = huskyField;
}
#Override
public String toString() {
return "Husky{" +
"huskyField='" + huskyField + '\'' +
'}';
}
}
public static void main(String[] args) throws IOException {
List<Dog> dogs;
dogs = Arrays.asList(new Dog("aField", "dogField"), new Husky("hField", "dField2", "hField"));
ObjectMapper mapper = new ObjectMapper();
mapper.registerSubtypes(Dog.class, Husky.class);
TypeReference<List<Dog>> referenceType = new TypeReference<List<Dog>>() {
};
String json = mapper.writerWithDefaultPrettyPrinter().withType(referenceType).writeValueAsString(dogs);
System.out.println(json);
System.out.println(mapper.readValue(json, referenceType));
}
}
Output:
[ {
"type" : "dog",
"animalField" : "aField",
"dogField" : "dogField"
}, {
"type" : "husky",
"animalField" : "hField",
"dogField" : "dField2",
"huskyField" : "hField"
} ]
[Dog{dogField='dogField'}, Husky{huskyField='hField'}]
If it doesn't help please provide more code.