I spend some time today writing custom serializers and deserializers
to enable serialization and deserialization of objects in a hierarchy.
I believe I may have stumbled upon some behavior which, if not un-
intentional, is at least not very clearly documented.
So I have a simple interface IAnimal which is implemented by two
classes: Dog and Cat. The object of the exercise is to take IAnimal
objects serialize them and then deserialize them back and of course
preserve the class information. I have discovered that by using the
registerTypeHierarchyAdapter I have to create three adapters in total:
IAnimalAdapter, DogAdapter and CatAdapter. Whereas when using
registerTypeAdapter only one adapter is required: IAnimalAdapter. The
advantage of using registerTypeAdapter is obvious as the Dog and Cat
classes can evolve, e.g. have new fields added and they don't need any
special Adapters to be maintained whereas when using
registerTypeHierarchyAdapter one has to maintain the Adapters as well.
If that's the case then I am wondering, what is the use case for
registerTypeHierarchyAdapter?
Below I paste the code (without package names and imports with some
minor manual rearrangements to save space)
///////////// CASE 1 :: when using registerTypeAdapter //////////
public interface IAnimal { public String sound(); }
public class Cat implements IAnimal {
public String name;
public Cat(String name) {
this.name = name; }
public Cat(){}
@Override
public String sound() { return name+" : \"meaow\""; };
}
public class Dog implements IAnimal {
public String name;
public int ferocity;
public Dog(String name, int ferocity) {
this.name = name;
this.ferocity = ferocity;
}
public Dog(){}
@Override
public String sound() { return name+" : \"bark\" (ferocity
level:"+ferocity + ")"; }
}
public class IAnimalAdapter implements JsonSerializer<IAnimal>,
JsonDeserializer<IAnimal>{
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
@Override
public JsonElement serialize(IAnimal src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject retValue = new JsonObject();
String className = src.getClass().getCanonicalName();
retValue.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(src);
retValue.add(INSTANCE, elem);
return retValue;
}
@Override
public IAnimal deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
String className = prim.getAsString();
Class<?> klass = null;
try {
klass = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e.getMessage()); // whatever; this is
just an example
}
return context.deserialize(jsonObject.get(INSTANCE), klass);
}
}
public class Test {
public static void main(String[] args) {
IAnimal animals[] = new IAnimal[]{new Cat("Kitty"), new
Dog("Brutus", 5)};
Gson gsonExt = null;
{
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(IAnimal.class , new IAnimalAdapter());
gsonExt = builder.create();
}
for (IAnimal animal : animals) {
String animalS2 = gsonExt.toJson(animal, IAnimal.class);
System.out.println("serialized with the custom serializer:"
+animalS2);
IAnimal animal2 = gsonExt.fromJson(animalS2, IAnimal.class);
System.out.println(animal2.sound());
}
}
}
///////////// CASE 2 :: when using
registerTypeHierarchyAdapter //////////
IAnimal, IAnimalAdapter, Cat and Dog as before. In the Test::main we
now do the following registrations of adapters:
public static void main(String[] args) {
...
Gson gsonExt = null;
{
GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(IAnimal.class , new
IAnimalAdapter());
builder.registerTypeHierarchyAdapter(Cat .class , new
CatAdapter());
builder.registerTypeHierarchyAdapter(Dog .class , new
DogAdapter());
gsonExt = builder.create();
}
...
} // rest of the Test::main code remains the same
In this case, two new adapters have to be defined: CatAdapter and
DogAdapter:
public class CatAdapter implements JsonSerializer<Cat>,
JsonDeserializer<Cat> {
@Override
public JsonElement serialize(Cat src, Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(
src.name);
}
@Override
public Cat deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
JsonPrimitive prim = json.getAsJsonPrimitive();
return new Cat(prim.getAsString());
}
}
public class Dog implements IAnimal {
public String name;
public int ferocity;
public Dog(String name, int ferocity) {
super();
this.name = name;
this.ferocity = ferocity;
}
@Override
public String sound() {
return name+" : \"bark\" (ferocity level:"+ferocity + ")";
}
}
Given the above, what exactly is the purpose of
registerTypeHierarchyAdapter and how is it different from
registerTypeAdapter ?