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.
Related
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.
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.
I was playing around with polymorphism in combination with Jackson. I have a working example but one thing is a bit weird for me. When generating the Json I get a duplicate field.
I have the following tree: Garden -> Animal -> Dog or Cat.
#Value
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
public class Garden {
public String location;
public Animal animal;
}
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "name", visible = true, defaultImpl = Dog.class)
#JsonSubTypes({
#JsonSubTypes.Type(value = Dog.class, name = "Dog"),
#JsonSubTypes.Type(value = Cat.class, name = "Cat")}
)
public interface Animal {
}
#Value
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
public class Cat implements Animal {
public String name;
public String nickname;
}
#Value
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
public class Dog implements Animal {
public String name;
public String command;
}
The program:
public class App {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
Animal animal = new Dog("Dog", "Sit");
Garden garden = new Garden("Utrecht", animal);
try {
String gardenJson = objectMapper.writeValueAsString(garden);
System.out.println(gardenJson);
Garden deserializedDog = objectMapper.readValue("{\"location\":\"Utrecht\",\"animal\":{\"name\":\"Dog\",\"command\":\"Sit\"}}", Garden.class);
System.out.println("");
} catch (Exception e) {
e.printStackTrace();
}
}
}
When I deserialize from Json to Java, everything runs as expected (Using the following Json: {"location":"Utrecht","animal":{"name":"Dog","command":"Sit"}}). But when generating Json:
{"location":"Utrecht","animal":{"name":"Dog","name":"Dog","command":"Sit"}}
How to get rid of the duplicate name property?
Replace EXTERNAL_PROPERTY with EXISTING_PROPERTY.
So your example line:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "name", visible = true, defaultImpl = Dog.class)
needs to be replaced by:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "name", visible = true, defaultImpl = Dog.class)
I'm currently working on a model which uses generics and is little complicated. I understand that similar questions have been answered but none of them clearly answers mine.
Here is my model:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes(
{
#Type(value = Cls2.class, name = "Cls2")
})
abstract class Cls1<T> implements Serializable
{
private T myObj;
public T getMyObj()
{
return myObj;
}
public Cls1(T obj)
{
myObj = obj;
}
#JsonTypeName("Cls2")
public static class Cls2<E extends Int1> extends Cls1<E> implements Serializable
{
public Cls2()
{
super(null);
}
}
}
#JsonTypeName("ChildContainer")
class ChildContainer extends ParentContainer<OtherBean>
{
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes(
{
#Type(value = ChildContainer.class, name = "ChildContainer")
})
class ParentContainer<T extends RootBean> implements Int1
{
}
#JsonTypeName("OtherBean")
class OtherBean extends RootBean
{
}
#JsonTypeName("RootBean")
class RootBean implements Int1
{
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT, property = "type")
#JsonSubTypes(
{
#Type(value = RootBean.class, name = "RootBean"),
#Type(value = OtherBean.class, name = "OtherBean")
})
interface Int1 extends Serializable
{
}
My goal is to serialize and deserialze using jackson as follows:
public static void main(String[] args) throws Exception
{
Cls2<ChildContainer> req = new Cls2<ChildContainer>();
File file = new File("==some-file-path==");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(file, req);
//read it back using mapper.readValue(file, clazz) --Not sure about this
}
I get the following java.lang.StackOverflowError during the serialization:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.Class.getDeclaringClass(Native Method)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:290)
at org.codehaus.jackson.map.type.TypeBindings._resolve(TypeBindings.java:221)
at org.codehaus.jackson.map.type.TypeBindings.findType(TypeBindings.java:138)
at org.codehaus.jackson.map.type.TypeFactory._fromVariable(TypeFactory.java:951)
at org.codehaus.jackson.map.type.TypeFactory._constructType(TypeFactory.java:493)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:423)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:395)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:299)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:290)
at org.codehaus.jackson.map.type.TypeBindings._resolve(TypeBindings.java:221)
at org.codehaus.jackson.map.type.TypeBindings.findType(TypeBindings.java:138)
at org.codehaus.jackson.map.type.TypeFactory._fromVariable(TypeFactory.java:951)
at org.codehaus.jackson.map.type.TypeFactory._constructType(TypeFactory.java:493)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:423)
at org.codehaus.jackson.map.type.TypeFactory.findTypeParameters(TypeFactory.java:395)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:299)
at org.codehaus.jackson.map.type.TypeBindings._resolveBindings(TypeBindings.java:290)
Any help is deeply appreciated.
The stacktrace suggests an infinite recursion in type resolving of Cls2 which extends the class it is by itself nested in. This seems to be a corner case bug in Jackson (report it!). In the meanwhile, extracting Cls2 into a standalone class instead of nesting it in its superclass should solve this problem.