registerTypeAdapter versus registerTypeHierarchyAdapter

11,580 views
Skip to first unread message

Menelaos Perdikeas

unread,
Dec 30, 2011, 12:24:00 PM12/30/11
to google-gson
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 ?



Inderjeet Singh

unread,
Dec 31, 2011, 3:05:35 AM12/31/11
to googl...@googlegroups.com
The purpose of typeHierarchyAdapter is to cover the case when you want the same representation for *all* subtypes of a type. Note that you are allowed to override for a specific sub-type as well. This behavior is useful when you are serializing common container objects such as Set, Map, Collection (Gson handles those automatically). 
Another example would be a sub-type of Joda-Time DateTime. You dont really care about what the details of sub-types are, you just want to write out the date as string.

Your case isn't a good use of type hierarchy adapter since you want the extra fields of Dog and Cat to go out in JSON. But if name was a property of IAnimal and that was the only thing you cared to put in JSON for all sub-types of animals, registerTypeHierarchyAdapter would have proved useful. Then you would just register a type hierarchy adapter for IAnimal and all sub-types of IAnimal get invoked with it (you dont have to worry about which sub-types are registered vs not). 

HTH
Inder

David Nouls

unread,
Oct 25, 2013, 7:33:09 AM10/25/13
to googl...@googlegroups.com
And what would be a good approach for this very typical problem most of us will experience ?

Best Regards,
David

Inderjeet Singh

unread,
Dec 6, 2013, 1:38:16 PM12/6/13
to googl...@googlegroups.com
You could consider writing a TypeAdapterFactory that takes care of the entire type hierarchy.

David

unread,
Dec 9, 2013, 7:55:58 AM12/9/13
to googl...@googlegroups.com
Inder,
 
Can you show us an example how to properly do this ? I am totally confused! It would be nice if GSON would be a bit more helpful in supporting something that we take for granted in normal OO designs. Something like a serialisation strategy when you have aggregation would be realy nice.
 
I have something like this:
class Request {
}
 
class LoginRequest extends Request {
}
 
class LogoutRequest extends Request {
}
 
class WrapperClass {
  private Request request; // can be a subtype
}
 
What I need is that everywhere where the Request type is referred, that it writes out something to the json stream to indicate the type (classname, token, whatever, let the strategy decide on the how), and that it then serializes the actual type, not just he common ones like the addTypeHierarchyAdapter is doing.
 
I don't want to start writing custom serializers for all possible classes that might use aggregation. What is acceptable is that I need to register all possible subtypes manually in the gson. What is also acceptable is that I could configure how to do the mapping (sending the java classname does not sound like a great idea for payload size and especially for security reasons).
 
 
David
 
 


--
You received this message because you are subscribed to the Google Groups "google-gson" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-gson...@googlegroups.com.
To post to this group, send email to googl...@googlegroups.com.
Visit this group at http://groups.google.com/group/google-gson.
For more options, visit https://groups.google.com/groups/opt_out.

Shivangi Gandhi

unread,
Nov 12, 2014, 6:10:01 PM11/12/14
to googl...@googlegroups.com, david...@gmail.com
Was there any resolution to David's question? Thanks!

Inderjeet Singh

unread,
Dec 2, 2014, 7:43:31 PM12/2/14
to googl...@googlegroups.com, david...@gmail.com
For an example, look at:

CIrcle and Square extend Shape.
Shape.JsonAdapterFactory is used to automatically serialize/deserialize objects to Circle or Square depending on the type field.

The example also has a nice use of the new @JsonAdapter annotation.
Inder

Liao Hexiang

unread,
Jan 7, 2015, 10:34:43 PM1/7/15
to googl...@googlegroups.com, david...@gmail.com
Hi Inder,

the nice example shown below is using the RuntimeTypeAdapterFactory which is not yet released? can it be used standalone?

Thanks,
Tony

Charles

unread,
Aug 23, 2015, 5:47:23 PM8/23/15
to google-gson
Hello Inderjeet,
I am currently using GSON lib, and i run into real headache.
Here my code:

public class StateMachine {
.......
    private Map<Integer, State> states = new HashMap<>();
.......
}

I serialize the entire StateMachine Object. 
State have to derived class.

When i deserialize, i get only State. Not specific class. I tryed to look into JsonDeserializer. But... i dont really understand how i can use it because i use a Map.

Can you give me some advise ?

Thank you in advance.

Charles.

Inderjeet Singh

unread,
Aug 24, 2015, 12:15:44 PM8/24/15
to google-gson
Register a JsonTypeAdapterFactory for State.class. This factory should extend RuntimeTypeAdapterFactory and use a type field to figure out how to deserialize state objects into correct sub-classes.

narendra babu

unread,
May 29, 2016, 7:36:51 PM5/29/16
to google-gson

Hello Indrajeet,

I am having almost similar problem. Following is my class structure

Basically I was getting WhLoc1 JSON from external module.
Now they have changed it to WhLoc2.

WhLoc1 ->  LocGroup1, OrgUnit1 and other elements
WhLoc2 -> LocGroup2, OrgUnit2 and other elements

(Elements and structure of  WhLoc, LocGroup, OrgUnit is changed here and there)

until we adopt completely WhLoc2, we need both. 
So I have created an interface for WhLoc, LocGroup and OrgUnit.

Based on Flag I know what is the JSON I am dealing with either WhLoc1/Whloc2
But it has ILocGroup and IOrgunit.

I just want to know how I can deserialize the ILocGroup within WhLoc based on the flag to one of its implementation

public Interface class ILoc
{
   Integer getID;
   String getName();
   String getLocType();
   ILocGroup getGroup();
   IOrgUnit getOrgUnit();

}

@XML Element
public Loc1 implements ILoc
{
   Integer ID;
   String Name;
   LocType type:
   ILocGroup LocGroup;
   IOrgUnit orgUnit;
 
   LocType getType() {
        return type
   }

   Sgring getLocType() {
        return getType() .getName();
   }

}

@XML Element
public Loc2 implements ILoc
{
   Integer ID;
   String Name;
  String type;
   ILocGroup LocGroup2;  // It has LocGroup2 Format
   IOrgUnit orgUnit2; // It has LocGroup2 Format
 
   String getType() {
        return type;
   }

   Sgring getLocType() {
        return getType();
   }




Reply all
Reply to author
Forward
0 new messages