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.
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.
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.
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).
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.
Here is a generic solution that works for all cases where only interface is known statically.
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; } }
make Gson use it for the interface type of your choice:
Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter<Animal>()) .create();
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With