Java: Jackson Polymorphic JSON Deserialization of an Object with an Interface Property

Java: Jackson polymorphic JSON deserialization of an object with an interface property?

You should use JsonTypeInfo.As.EXTERNAL_PROPERTY instead of JsonTypeInfo.As.PROPERTY. In this scenario your Asset class should look like this:

class Asset {

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = ImageAssetProperties.class, name = "image"),
@JsonSubTypes.Type(value = DocumentAssetProperties.class, name = "document") })
private AssetProperties properties;

public AssetProperties getProperties() {
return properties;
}

public void setProperties(AssetProperties properties) {
this.properties = properties;
}

@Override
public String toString() {
return "Asset [properties("+properties.getClass().getSimpleName()+")=" + properties + "]";
}
}

See also my answer in this question: Jackson JsonTypeInfo.As.EXTERNAL_PROPERTY doesn't work as expected.

Deserializing polymorphic types with Jackson based on the presence of a unique property

This feels like something @JsonTypeInfo and @JsonSubTypes should be used for but I've picked through the docs and none of the properties that can be supplied quite seem to match what you're describing.

You could write a custom deserializer that uses @JsonSubTypes' "name" and "value" properties in a non-standard way to accomplish what you want. The deserializer and @JsonSubTypes would be supplied on your base class and the deserializer would use the "name" values to check for the presence of a property and if it exists, then deserialize the JSON into the class supplied in the "value" property. Your classes would then look something like this:

@JsonDeserialize(using = PropertyPresentDeserializer.class)
@JsonSubTypes({
@Type(name = "stringA", value = SubClassA.class),
@Type(name = "stringB", value = SubClassB.class)
})
public abstract class Parent {
private Long id;
...
}

public class SubClassA extends Parent {
private String stringA;
private Integer intA;
...
}

public class SubClassB extends Parent {
private String stringB;
private Integer intB;
...
}

Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error

As promised, I'm putting an example for how to use annotations to serialize/deserialize polymorphic objects, I based this example in the Animal class from the tutorial you were reading.

First of all your Animal class with the Json Annotations for the subclasses.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),

@JsonSubTypes.Type(value = Cat.class, name = "Cat") }
)
public abstract class Animal {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

Then your subclasses, Dog and Cat.

public class Dog extends Animal {

private String breed;

public Dog() {

}

public Dog(String name, String breed) {
setName(name);
setBreed(breed);
}

public String getBreed() {
return breed;
}

public void setBreed(String breed) {
this.breed = breed;
}
}

public class Cat extends Animal {

public String getFavoriteToy() {
return favoriteToy;
}

public Cat() {}

public Cat(String name, String favoriteToy) {
setName(name);
setFavoriteToy(favoriteToy);
}

public void setFavoriteToy(String favoriteToy) {
this.favoriteToy = favoriteToy;
}

private String favoriteToy;

}

As you can see, there is nothing special for Cat and Dog, the only one that know about them is the abstract class Animal, so when deserializing, you'll target to Animal and the ObjectMapper will return the actual instance as you can see in the following test:

public class Test {

public static void main(String[] args) {

ObjectMapper objectMapper = new ObjectMapper();

Animal myDog = new Dog("ruffus","english shepherd");

Animal myCat = new Cat("goya", "mice");

try {
String dogJson = objectMapper.writeValueAsString(myDog);

System.out.println(dogJson);

Animal deserializedDog = objectMapper.readValue(dogJson, Animal.class);

System.out.println("Deserialized dogJson Class: " + deserializedDog.getClass().getSimpleName());

String catJson = objectMapper.writeValueAsString(myCat);

Animal deseriliazedCat = objectMapper.readValue(catJson, Animal.class);

System.out.println("Deserialized catJson Class: " + deseriliazedCat.getClass().getSimpleName());

} catch (Exception e) {
e.printStackTrace();
}

}
}

Output after running the Test class:

{"@type":"Dog","name":"ruffus","breed":"english shepherd"}

Deserialized dogJson Class: Dog

{"@type":"Cat","name":"goya","favoriteToy":"mice"}

Deserialized catJson Class: Cat

Deserializing json subtype based on parent property

You can achieve that by adding include = As.EXTERNAL_PROPERTY to @JsonTypeInfo. You just have to move the annotation to the field.

See the JavaDoc for EXTERNAL_PROPERTY:

Inclusion mechanism similar to PROPERTY, except that property is included one-level higher in hierarchy [...]

Here's an example:

@Data
class Field {
private String label;
private AttributeType attributeType;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.EXTERNAL_PROPERTY, property = "attributeType")
private Attribute attribute;
}

@Data
@JsonSubTypes({
@JsonSubTypes.Type(value = Attribute.NumberAttribute.class, name = "NUMBER"),
@JsonSubTypes.Type(value = Attribute.TextAttribute.class, name = "TEXT")
})
abstract class Attribute {
@Data
public static class TextAttribute extends Attribute {
List<Language> languages;
}

@Data
public static class NumberAttribute extends Attribute {
String value;
}

@Data
public static class Language {
private String text;
private String language;
}
}

enum AttributeType {
NUMBER, TEXT;
}


Related Topics



Leave a reply



Submit