Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle deserializing with polymorphism?

Tags:

java

gson

I have a class like:

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

and I want to serialize a list of them:

List<Barn> barns = new ArrayList<Barn>();  Barn barn1 = new Barn(); barn1.setType("horse"); barn1.setAnimal(new Horse()); barns.add(barn1);  Barn barn2 = new Barn(); barn2.setType("cow"); barn2.setAnimal(new Cow()); barns.add(barn2);  ...  Group<Barn> barns = gson.fromJson(serialized);    

When I serialize, type information will be lost for the Animal attribute. Is there a way I could install a parser listener somehow so that I could provide the right class to deserialize as when each element in the list is encountered? That was the idea behind manually supplying a string describing the class type.

Thanks

like image 265
user1219278 Avatar asked Apr 01 '13 01:04

user1219278


People also ask

What is polymorphic deserialization?

A polymorphic deserialization allows a JSON payload to be deserialized into one of the known gadget classes that are documented in SubTypeValidator. java in jackson-databind in GitHub. The deserialized object is assigned to a generic base class in your object model, such as java. lang.

What is Deserializing a JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).

What is JsonTypeInfo?

@JsonTypeInfo is used to indicate details of type information which is to be included in serialization and de-serialization.

What is @JsonTypeName?

@JsonTypeName is used to set type names to be used for annotated class.


2 Answers

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 {} 
like image 112
Programmer Bruce Avatar answered Oct 02 '22 16:10

Programmer Bruce


You could use Gson Fire for this. The code would look something like this:

GsonFireBuilder builder = new GsonFireBuilder()     .registerTypeSelector(Barn.class, new TypeSelector<Barn>() {         @Override         public Class<? extends Barn> getClassForElement(JsonElement readElement) {             String type = readElement.getAsJsonObject().get("type").getAsString();             if(type.equals("horse")){                 return Horse.class;              } else if(type.equals("cow")) {                 return Cow.class;             } else {                 return null; //returning null will trigger Gson's default behavior             }         }     }); Gson gson = builder.createGson(); 
like image 41
julman99 Avatar answered Oct 02 '22 16:10

julman99