How to Handle Deserializing with Polymorphism

How to handle deserializing with polymorphism?

In the Gson project code base is the RuntimeTypeAdapter, which reportedly works well for polymorphic serialization and deserialization. I don't think I've yet tried to use it. See http://code.google.com/p/google-gson/issues/detail?id=231 for more info. Note, it hasn't yet been included in any Gson releases.

If use of it doesn't fit your needs, then custom deserialization processing is necessary. Following is one such approach, assuming you want to use the JSON structure demonstrated. (I'd take a somewhat different approach, if the JSON structure could be different.)

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;

public class App
{
public static void main(String[] args)
{
Barn[] barns = {new Barn(), new Barn()};
barns[0].type = "horse";
barns[0].animal = new Horse();
barns[1].type = "cow";
barns[1].animal = new Cow();

String json = new Gson().toJson(barns);
// [{"type":"horse","animal":{}},{"type":"cow","animal":{}}]

BarnDeserializer deserializer = new BarnDeserializer("type");
deserializer.registerBarnType("horse", Horse.class);
deserializer.registerBarnType("cow", Cow.class);
Gson gson = new GsonBuilder().registerTypeAdapter(Barn.class, deserializer).create();

List<Barn> barns2= gson.fromJson(json, new TypeToken<List<Barn>>(){}.getType());
for (Barn barn : barns2)
{
System.out.println(barn.animal.getClass());
}
}
}

class BarnDeserializer implements JsonDeserializer<Barn>
{
String barnTypeElementName;
Gson gson;
Map<String, Class<? extends Animal>> barnTypeRegistry;

BarnDeserializer(String barnTypeElementName)
{
this.barnTypeElementName = barnTypeElementName;
gson = new Gson();
barnTypeRegistry = new HashMap<>(); // Java 7 required for this syntax.
}

void registerBarnType(String barnTypeName, Class<? extends Animal> animalType)
{
barnTypeRegistry.put(barnTypeName, animalType);
}

@Override
public Barn deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
JsonObject barnObject = json.getAsJsonObject();
JsonElement animalTypeElement = barnObject.get(barnTypeElementName);
Barn barn = new Barn();
barn.type = animalTypeElement.getAsString();
Class<? extends Animal> animalType = barnTypeRegistry.get(barn.type);
barn.animal = gson.fromJson(barnObject.get("animal"), animalType);
return barn;
}
}

class Barn {String type; Animal animal;}
class Animal {}
class Horse extends Animal {}
class Cow extends Animal {}

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

Deserialize List of polymorphic objects into Object field

I express sincere gratitude to all who participated. I decided to go with a little bit introspective approach and wrote custom deserializer that looks inside first object in list to figure out if it needs to deserialize this as List.

public class AnimalsDeserializer extends JsonDeserializer<Object> {

private Set<String> subtypes = Set.of("dog", "cat");

@Override
public Object deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException {
if (parser.getCurrentToken() == JsonToken.START_ARRAY) {
TreeNode node = parser.getCodec().readTree(parser);
TreeNode objectNode = node.get(0);
if (objectNode == null) {
return List.of();
}
TreeNode type = objectNode.get("name");
if (type != null
&& type.isValueNode()
&& type instanceof TextNode
&& subtypes.contains(((TextNode) type).asText())) {
return parser.getCodec().treeAsTokens(node).readValueAs(new TypeReference<ArrayList<Animal>>() {
});
}
}
return parser.readValueAs(Object.class);
}
}

Maybe some checks are redundant. It works and it's all that matters :).
Award goes to StephanSchlecht as author of most inspirational answer. Thanks again!

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;
...
}

Jackson Polymorphic Deserialization via field

Assuming that your JSON documents are like:

{
"type": "foo",
"data": {
"someCommonProperty": "common property",
"fooProperty": "foo specific property"
}
}
{
"type": "bar",
"data": {
"someCommonProperty": "common property",
"barProperty": "bar specific property"
}
}

You can use:

public class Wrapper {

private String type;

@JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = Foo.class, name = "foo"),
@JsonSubTypes.Type(value = Bar.class, name = "bar")
})
private AbstractData data;

// Getters and setters
}
public abstract class AbstractData {

private String someCommonProperty;

// Getters and setters
}
public class Foo extends AbstractData {

private String fooProperty;

// Getters and setters
}
public class Bar extends AbstractData {

private String barProperty;

// Getters and setters
}

In this approach, @JsonTypeInfo is set to use type as an external property to determine the right class to map the data property. The JSON document can be deserialized as following:

ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);

Deserializing polymorphic json classes without type information using json.net

You can do this fairly easily by creating a custom JsonConverter to handle the object instantiation. Assuming you have your classes defined something like this:

public abstract class GalleryItem
{
public string id { get; set; }
public string title { get; set; }
public string link { get; set; }
public bool is_album { get; set; }
}

public class GalleryImage : GalleryItem
{
// ...
}

public class GalleryAlbum : GalleryItem
{
public int images_count { get; set; }
public List<GalleryImage> images { get; set; }
}

You would create the converter like this:

public class GalleryItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(GalleryItem).IsAssignableFrom(objectType);
}

public override object ReadJson(JsonReader reader,
Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);

// Using a nullable bool here in case "is_album" is not present on an item
bool? isAlbum = (bool?)jo["is_album"];

GalleryItem item;
if (isAlbum.GetValueOrDefault())
{
item = new GalleryAlbum();
}
else
{
item = new GalleryImage();
}

serializer.Populate(jo.CreateReader(), item);

return item;
}

public override bool CanWrite
{
get { return false; }
}

public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Here's an example program showing the converter in action:

class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""id"": ""OUHDm"",
""title"": ""My most recent drawing. Spent over 100 hours."",
""link"": ""http://i.imgur.com/OUHDm.jpg"",
""is_album"": false
},
{
""id"": ""lDRB2"",
""title"": ""Imgur Office"",
""link"": ""http://alanbox.imgur.com/a/lDRB2"",
""is_album"": true,
""images_count"": 3,
""images"": [
{
""id"": ""24nLu"",
""link"": ""http://i.imgur.com/24nLu.jpg""
},
{
""id"": ""Ziz25"",
""link"": ""http://i.imgur.com/Ziz25.jpg""
},
{
""id"": ""9tzW6"",
""link"": ""http://i.imgur.com/9tzW6.jpg""
}
]
}
]";

List<GalleryItem> items =
JsonConvert.DeserializeObject<List<GalleryItem>>(json,
new GalleryItemConverter());

foreach (GalleryItem item in items)
{
Console.WriteLine("id: " + item.id);
Console.WriteLine("title: " + item.title);
Console.WriteLine("link: " + item.link);
if (item.is_album)
{
GalleryAlbum album = (GalleryAlbum)item;
Console.WriteLine("album images (" + album.images_count + "):");
foreach (GalleryImage image in album.images)
{
Console.WriteLine(" id: " + image.id);
Console.WriteLine(" link: " + image.link);
}
}
Console.WriteLine();
}
}
}

And here is the output of the above program:

id: OUHDm
title: My most recent drawing. Spent over 100 hours.
link: http://i.imgur.com/OUHDm.jpg

id: lDRB2
title: Imgur Office
link: http://alanbox.imgur.com/a/lDRB2
album images (3):
id: 24nLu
link: http://i.imgur.com/24nLu.jpg
id: Ziz25
link: http://i.imgur.com/Ziz25.jpg
id: 9tzW6
link: http://i.imgur.com/9tzW6.jpg

Fiddle: https://dotnetfiddle.net/1kplME



Related Topics



Leave a reply



Submit