Gson Polymorphic Serialization

using Gson 2.2.2 I am trying to serialize a list of POJO arrays (Behaviors).

I have an adapter that almost copies what I saw on the Internet:

public class BehaviorAdapter implements JsonSerializer<Behavior> { private static final String CLASSNAME = "CLASSNAME"; private static final String INSTANCE = "INSTANCE"; @Override public JsonElement serialize(Behavior 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; } } 

I will register it as follows:

 GsonBuilder builder = new GsonBuilder(); builder.registerTypeHierarchyAdapter(Behavior.class, new BehaviorAdapter()); gson = builder.create(); 

Then when I try to serialize my ArrayList:

 String json2 = gson.toJson(behaviors); 

I get a stack overflow.

It looks like in line:

 JsonElement elem = context.serialize(src); 

It starts a recursive loop repeating over and over through my serializer. So, how do I register to prevent this from happening? I need to serialize a list and maintain polymorphism.

+3
java gson
Nov 06 '12 at 5:11
source share
3 answers

Looks like you found an endless loop of warning JsonSerializer docs about :

However, you should never reference the src object, as this will cause an infinite loop (Gson will call your callback method again).

The easiest way I can come up with is to create a new Gson instance that does not have a handler installed, and run your instances through this.

As a final run, you can simply serialize the List<Behavior> instead:

 public class BehaviorListAdapter implements JsonSerializer<List<Behavior>> { private static final String CLASSNAME = "CLASSNAME"; private static final String INSTANCE = "INSTANCE"; @Override public JsonElement serialize(List<Behavior> src, Type typeOfSrc, JsonSerializationContext context) { JsonArray array = new JsonArray(); for (Behavior behavior : src) { JsonObject behaviorJson = new JsonObject(); String className = behavior.getClass().getCanonicalName(); behaviorJson.addProperty(CLASSNAME, className); JsonElement elem = context.serialize(behavior); behaviorJson.add(INSTANCE, elem); array.add(behaviorJson); } return array; } } GsonBuilder builder = new GsonBuilder(); // use a TypeToken to make a Type instance for a parameterized type builder.registerTypeAdapter( (new TypeToken<List<Behavior>>() {}).getType(), new BehaviorListAdapter()); gson = builder.create(); 
+8
Nov 06 '12 at 7:24
source share

Take a look at the RuntimeTypeAdapterFactory . test for this class has an example:

 RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of( BillingInstrument.class) .registerSubtype(CreditCard.class); Gson gson = new GsonBuilder() .registerTypeAdapterFactory(rta) .create(); CreditCard original = new CreditCard("Jesse", 234); assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}", gson.toJson(original, BillingInstrument.class)); BillingInstrument deserialized = gson.fromJson( "{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class); assertEquals("Jesse", deserialized.ownerName); assertTrue(deserialized instanceof CreditCard); 

This class is not the core core of Gson; you will need to copy it into your project in order to use it.

+6
Nov 06 '12 at 7:11
source share

I get what you are trying to do here and I had the same problem.

I finished writing a simple abstract class

 public abstract class TypedJsonizable extends Jsonizable {} 

and register TypeHierarchy adapter in my Gson instance

  protected static Gson gson = new GsonBuilder() .registerTypeHierarchyAdapter (TypedJsonizable.class,new TypedJsonizableSerializer()); 

The key for this TypeAdapter should not call context.serialize and context.deserialize, because it will lead to the endless loop pointed out by Jeff Bowman in his answer, This is To avoid this, use the TypeAdapter.

 import com.google.gson.*; import org.apache.log4j.Logger; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; public class TypedJsonizableSerializer implements JsonSerializer<TypedJsonizable>, JsonDeserializer<TypedJsonizable> { static final String CLASSNAME_FIELD = "_className"; Logger logger = Logger.getLogger(TypedJsonizable.class); @Override public JsonElement serialize(TypedJsonizable src, Type typeOfSrc, JsonSerializationContext context) { JsonObject contentObj = new JsonObject(); contentObj.addProperty(CLASSNAME_FIELD,src.getClass().getCanonicalName()); for (Field field : src.getClass().getDeclaredFields()) { field.setAccessible(true); try { if (field.get(src)!=null) contentObj.add(field.getName(),context.serialize(field.get(src))); } catch (IllegalAccessException e) { logger.error(e.getMessage(),e); } } return contentObj; } @Override public TypedJsonizable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); String className = jsonObject.get(CLASSNAME_FIELD).getAsString(); if (className == null || className.isEmpty()) throw new JsonParseException("Cannot find _className field. Probably this instance has not been serialized using Jsonizable jsonizer"); try { Class<?> clazz = Class.forName(className); Class<?> realClazz = (Class<?>) typeOfT; if (!realClazz.equals(clazz)) throw new JsonParseException(String.format("Cannot serialize object of class %s to %s", clazz.getCanonicalName(),realClazz.getCanonicalName())); Object o = clazz.getConstructor().newInstance(); for (Field field : o.getClass().getDeclaredFields()) { field.setAccessible(true); if (jsonObject.has(field.getName())) { field.set(o,context.deserialize(jsonObject.get(field.getName()) , field.getGenericType())); } } return (TypedJsonizable) o; } catch (ClassNotFoundException e) { throw new JsonParseException(String.format("Cannot find class with name %s . Maybe the class has been refactored or sender and receiver are not using the same jars",className)); } catch (IllegalAccessException e){ throw new JsonParseException(String.format("Cannot deserialize, got illegalAccessException %s ",e.getMessage())); } catch (NoSuchMethodException | InstantiationException | InvocationTargetException e) { throw new JsonParseException(String.format("Cannot deserialize object of class %s, unable to create a new instance invoking empty constructor",className)); } } 

}

0
Jun 16 '17 at 11:19 on
source share



All Articles