¿Cómo serializar una clase con una interfaz?

Nunca he hecho demasiado con la serialización, pero estoy tratando de usar Googleson para serializar un objeto Java en un archivo. Aquí hay un ejemplo de mi problema:

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

Así que esto se serializa muy bien, pero comprensiblemente deja caer la información tipo sobre el Animal:

 {"mDescription":"This is a public exhibit.","mAnimal":{"mName":"Cat","mHabbit":"Playing with yarn"}} 

Esto causa problemas reales para la deserialización, sin embargo:

 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. 

Entiendo por qué sucede esto, pero estoy teniendo problemas para encontrar el patrón adecuado para manejar esto. Miré en la guía, pero no me ocupé de esto directamente.

Coloque al animal como transitorio, y luego no se serializará.

O puede serializarlo usted mismo implementando defaultWriteObject (…) y defaultReadObject (…) (creo que eso es como se llamaron …)

EDITAR Vea la parte sobre Escribir un creador de instancias aquí http://sites.google.com/site/gson/gson-user-guide

Gson no puede deserializar una interfaz ya que no sabe qué clase de implementación se utilizará, por lo que debe proporcionar un creador de la instancia para su Animal y establecer un valor predeterminado o similar.

Aquí hay una solución genérica que funciona para todos los casos donde solo la interfaz se conoce estáticamente.

  1. Crear serializador / deserializador:

     final class InterfaceAdapter implements JsonSerializer, JsonDeserializer { 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; } } 
  2. haga que Gson lo use para el tipo de interfaz de su elección:

     Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter()) .create(); 

La solución @Maciek funciona perfectamente si el tipo declarado de la variable miembro es la clase de interfaz / resumen. No funcionará si el tipo declarado es subclase / subinterfaz / subextracción a menos que los registremos todos a través de registerTypeAdapter() . Podemos evitar registrar uno por uno con el uso de registerTypeHierarchyAdapter , pero me doy cuenta de que causará StackOverflowError debido al bucle infinito. (Por favor, lea la sección de referencia a continuación)

En resumen, mi solución alternativa parece un poco insensata, pero funciona sin StackOverflowError .

 @Override public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) { final JsonObject wrapper = new JsonObject(); wrapper.addProperty("type", object.getClass().getName()); wrapper.add("data", new Gson().toJsonTree(object)); return wrapper; } 

Utilicé otra nueva instancia de trabajo de Gson como el serializador / deserializador predeterminado para evitar el bucle infinito. El inconveniente de esta solución es que también perderá otro TypeAdapter , si tiene serialización personalizada para otro tipo y aparece en el objeto, simplemente fallará.

Aún así, estoy esperando una mejor solución.

Referencia

Según la documentación de Gson 2.3.1 para JsonSerializationContext y JsonDeserializationContext

Invoca serialización predeterminada en el objeto especificado que pasa la información de tipo específico. Nunca se debe invocar en el elemento recibido como parámetro del método JsonSerializer.serialize (Object, Type, JsonSerializationContext). Hacerlo dará como resultado un ciclo infinito ya que Gson llamará el serializador personalizado de nuevo.

y

Invoca deserialización predeterminada en el objeto especificado. Nunca se debe invocar en el elemento recibido como parámetro del método JsonDeserializer.deserialize (JsonElement, Type, JsonDeserializationContext). Si lo hace, se producirá un ciclo infinito ya que Gson llamará nuevamente al deserializador personalizado.

Esto concluye que la implementación a continuación causará un ciclo infinito y causará StackOverflowError eventualmente.

 @Override public JsonElement serialize(Animal src, Type typeOfSrc, JsonSerializationContext context) { return context.serialize(src); } 

Tuve el mismo problema, excepto que mi interfaz era de tipo primitivo ( CharSequence ) y no JsonObject :

 if (elem instanceof JsonPrimitive){ JsonPrimitive primitiveObject = (JsonPrimitive) elem; Type primitiveType = primitiveObject.isBoolean() ?Boolean.class : primitiveObject.isNumber() ? Number.class : primitiveObject.isString() ? String.class : String.class; return context.deserialize(primitiveObject, primitiveType); } if (elem instanceof JsonObject){ 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); }