In this article we will compare polymorphic type handling in different JSON binding libraries in Java i.e. Jackson vs. Gson vs. JSON-B (Eclipse Yasson)
For complete comparison of top JSON libraries & their features, visit below article.
Polymorphic JSON bound POJOs
As Java is object oriented language, JSON bound POJOs might have polymorphic nature. For example, Car (abstraction) with Honda & Tesla as concrete implementations.
1 2 3 4 5 |
public class Garage { private Car car; ... Getter and setter ... } |
1 2 3 4 5 |
public abstract class Car { private String make; ... Getter and setter ... } |
1 2 3 4 5 6 |
public class Honda extends Car { @Override public String toString() { return "Driving Honda!"; } } |
1 2 3 4 5 6 |
public class Tesla extends Car { @Override public String toString() { return "Driving Tesla!"; } } |
Test JSON
For above POJOs, we will use these JSONs to test different libraries.
1 2 3 4 5 |
{ "car": { "make": "Honda" } } |
1 2 3 4 5 |
{ "car": { "make": "Tesla" } } |
Jackson
In jackson, to achieve polymorphic handling of JSON bound POJOs, just add these annotations on Car.java i.e. abstract class. To identify subclass to use during deserialization, value of ‘make’ field from subclasses will be used to determine which sub class will be used to instantiate object.
1 2 3 4 5 6 7 8 9 10 |
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "make") @JsonSubTypes({ @JsonSubTypes.Type(value = Honda.class, name = "Honda"), @JsonSubTypes.Type(value = Tesla.class, name = "Tesla") }) public abstract class Car { private String make; ...... } |
Now lets deserialize JSON using above annotated POJO & test json files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.io.File; import java.io.IOException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JacksonPolymorphism { public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException { ObjectMapper objectMapper = new ObjectMapper(); Garage garage_1 = objectMapper.readValue(new File("garage-1.json"), Garage.class); System.out.println(garage_1.getCar()); Garage garage_2 = objectMapper.readValue(new File("garage-2.json"), Garage.class); System.out.println(garage_2.getCar()); } } |
1 2 |
Driving Honda! Driving Tesla! |
GSON
GSON does not provide out of the box way to handle polymorphic type handling, but we can create custom deserializer to achieve this effect.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class CarDeserializer implements JsonDeserializer<Car> { @Override public Car deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws com.google.gson.JsonParseException { JsonObject jsonObj = json.getAsJsonObject(); String make = jsonObj.get("make").getAsString(); switch (make) { case "Honda": return context.deserialize(jsonObj, Honda.class); case "Tesla": return context.deserialize(jsonObj, Tesla.class); } return null; } } |
Now lets deserialize JSON using above CarDeserializer & test json files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import java.io.File; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Type; 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; public class GsonPolymorphism { public static void main(String[] args) throws IOException { Gson gson = new GsonBuilder().registerTypeAdapter(Car.class, new CarDeserializer()).create(); Garage garage_1 = gson.fromJson(new FileReader(new File("garage-1.json")), Garage.class); System.out.println(garage_1.getCar()); Garage garage_2 = gson.fromJson(new FileReader(new File("garage-2.json")), Garage.class); System.out.println(garage_2.getCar()); } } |
JSON-B
JSON-B also does not provide specific easy annotation to achieve this so we can create deserializer to achieve this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import java.lang.reflect.Type; import javax.json.JsonObject; import javax.json.bind.serializer.DeserializationContext; import javax.json.bind.serializer.JsonbDeserializer; import javax.json.stream.JsonParser; public class CarDeserializer implements JsonbDeserializer<Car> { @Override public Car deserialize(JsonParser parser, DeserializationContext context, Type rtType) { JsonObject jsonObj = parser.getObject(); String make = jsonObj.getString("make"); switch (make) { case "Honda": return context.deserialize(Honda.class, parser); case "Tesla": return context.deserialize(Tesla.class, parser); } return null; } } |
Put annotation on Car.java to user CarDeserializer
1 2 3 4 5 6 |
import javax.json.bind.annotation.JsonbTypeDeserializer; @JsonbTypeDeserializer(CarDeserializer.class) public abstract class Car { .... } |
Now lets deserialize JSON using above CarDeserializer & test json files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.io.File; import java.io.FileReader; import java.io.IOException; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; public class JsonBPolymorphism { public static void main(String[] args) throws IOException { Jsonb jsonb = JsonbBuilder.create(); /* * You can also register deserializer here instead of annotation in Car.java * * Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withDeserializers(new * CarDeserializer())); */ Garage garage_1 = jsonb.fromJson(new FileReader(new File("garage-1.json")), Garage.class); System.out.println(garage_1.getCar()); Garage garage_2 = jsonb.fromJson(new FileReader(new File("garage-2.json")), Garage.class); System.out.println(garage_2.getCar()); } } |
1 2 |
Driving Honda! Driving Tesla! |