Serialización polimórfica de Gson

utilizando Gson 2.2.2 Estoy intentando serializar una lista de arraigo de POJOs (Comportamientos).

Tengo un adaptador que es prácticamente una copia de lo que he visto en línea:

public class BehaviorAdapter implements JsonSerializer { 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; } } 

El lo registro así:

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

Entonces cuando bash serializar mi ArrayList:

 String json2 = gson.toJson(behaviors); 

Tengo un desbordamiento de stack.

Parece que en línea:

 JsonElement elem = context.serialize(src); 

Comienza un ciclo recursivo, yendo una y otra vez a través de mi serializador. Entonces, ¿cómo lo registro para que esto no suceda? Necesito serializar la lista y mantener el polymorphism.

Parece que has encontrado el bucle infinito que los documentos de JsonSerializer advierten sobre :

Sin embargo, nunca debe invocarlo en el propio objeto src, ya que eso causará un bucle infinito (Gson volverá a llamar a su método de callback).

La forma más fácil en la que puedo pensar es crear una nueva instancia de Gson que no tenga el controlador instalado y ejecutar las instancias a través de eso.

Como una ejecución final, simplemente podría serializar la List lugar:

 public class BehaviorListAdapter implements JsonSerializer> { private static final String CLASSNAME = "CLASSNAME"; private static final String INSTANCE = "INSTANCE"; @Override public JsonElement serialize(List 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>() {}).getType(), new BehaviorListAdapter()); gson = builder.create(); 

Echa un vistazo a RuntimeTypeAdapterFactory . La prueba para esa clase tiene un ejemplo:

 RuntimeTypeAdapterFactory 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); 

Esta clase no está en el núcleo Gson; necesitarás copiarlo en tu proyecto para usarlo.

Entiendo lo que intentas hacer aquí, y tuve el mismo problema.

Terminé escribiendo una clase abstracta simple

 public abstract class TypedJsonizable extends Jsonizable {} 

y registrar un TypeHierarchyAdapter en mi instancia de Gson

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

La clave para este TypeAdapter no es invocar context.serialize y context.deserialize porque esto causaría un bucle infinito como lo dijo Jeff Bowman en su respuesta, este TypeAdapter usa la reflexión para evitar eso.

 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, JsonDeserializer { 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)); } } 

}