Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize a class with an interface?

I have never done much with serialization, but am trying to use Google's gson to serialize a Java object to a file. Here is an example of my issue:

public interface Animal {     public String getName(); }    public class Cat implements Animal {      private String mName = "Cat";     private String mHabbit = "Playing with yarn";      public String getName() {         return mName;     }      public void setName(String pName) {         mName = pName;     }      public String getHabbit() {         return mHabbit;     }      public void setHabbit(String pHabbit) {         mHabbit = pHabbit;     }  }  public class Exhibit {      private String mDescription;     private Animal mAnimal;      public Exhibit() {         mDescription = "This is a public exhibit.";     }      public String getDescription() {         return mDescription;     }      public void setDescription(String pDescription) {         mDescription = pDescription;     }      public Animal getAnimal() {         return mAnimal;     }      public void setAnimal(Animal pAnimal) {         mAnimal = pAnimal;     }  }  public class GsonTest {  public static void main(String[] argv) {     Exhibit exhibit = new Exhibit();     exhibit.setAnimal(new Cat());     Gson gson = new Gson();     String jsonString = gson.toJson(exhibit);     System.out.println(jsonString);     Exhibit deserializedExhibit = gson.fromJson(jsonString, Exhibit.class);     System.out.println(deserializedExhibit); } } 

So this serializes nicely -- but understandably drops the type information on the Animal:

{"mDescription":"This is a public exhibit.","mAnimal":{"mName":"Cat","mHabbit":"Playing with yarn"}} 

This causes real problems for deserialization, though:

Exception in thread "main" java.lang.RuntimeException: No-args constructor for interface com.atg.lp.gson.Animal does not exist. Register an InstanceCreator with Gson for this type to fix this problem. 

I get why this is happening, but am having trouble figuring out the proper pattern for dealing with this. I did look in the guide but it didn't address this directly.

like image 638
Ben Flynn Avatar asked Jan 25 '11 15:01

Ben Flynn


People also ask

Can we serialize interface?

The interface must be implemented by any class which uses the interface for serializing its objects. By default, the classes such as wrapper classes and the String class implement the interface java.

How do you make an interface Serializable?

For serializing the object, we call the writeObject() method of ObjectOutputStream class, and for deserialization we call the readObject() method of ObjectInputStream class. We must have to implement the Serializable interface for serializing the object.

Can we serialize interface C#?

When the serializer should serialize that object it knows that the object implements that interface, all it really has to do is serialize it and attach the type attribute (like it does if you serialize abstract classes or just super-classes in general).

How do you serialize the class?

To serialize an object means to convert its state to a byte stream so that the byte stream can be reverted back into a copy of the object. A Java object is serializable if its class or any of its superclasses implements either the java. io. Serializable interface or its subinterface, java.


Video Answer


2 Answers

Here is a generic solution that works for all cases where only interface is known statically.

  1. Create serialiser/deserialiser:

    final class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {     public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {         final JsonObject wrapper = new JsonObject();         wrapper.addProperty("type", object.getClass().getName());         wrapper.add("data", context.serialize(object));         return wrapper;     }      public T deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {         final JsonObject wrapper = (JsonObject) elem;         final JsonElement typeName = get(wrapper, "type");         final JsonElement data = get(wrapper, "data");         final Type actualType = typeForName(typeName);          return context.deserialize(data, actualType);     }      private Type typeForName(final JsonElement typeElem) {         try {             return Class.forName(typeElem.getAsString());         } catch (ClassNotFoundException e) {             throw new JsonParseException(e);         }     }      private JsonElement get(final JsonObject wrapper, String memberName) {         final JsonElement elem = wrapper.get(memberName);         if (elem == null) throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");         return elem;     } } 
  2. make Gson use it for the interface type of your choice:

    Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter<Animal>())                              .create(); 
like image 79
narthi Avatar answered Sep 22 '22 20:09

narthi


Put the animal as transient, it will then not be serialized.

Or you can serialize it yourself by implementing defaultWriteObject(...) and defaultReadObject(...) (I think thats what they were called...)

EDIT See the part about "Writing an Instance Creator" here.

Gson cant deserialize an interface since it doesnt know which implementing class will be used, so you need to provide an instance creator for your Animal and set a default or similar.

like image 20
rapadura Avatar answered Sep 23 '22 20:09

rapadura