2017-05-16 2 views
2

Ich serialisieren eine Unterklassen des UserInterface-Typs basierend auf dem Typ des Benutzers. Diese Unterklassen haben eine Liste. Rolle ist auch eine Schnittstelle. Ich habe benutzerdefinierte Gson Adapter geschrieben:Json: Deserializing Interface-Klasse mit List of Interface generischer Typ

public class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> { 
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; 
} 
} 

Das Problem ist jedoch, dass ich Fehler: Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: Domain.Users.role.Role. Ich weiß, was das Problem ist. Gson weiß nicht, welche konkrete Implementierung zur Instanziierung gewählt werden sollte. Wie kann ich Gson mitteilen, welche Klasse instanziiert werden soll? Hier

ist voll Stacktrace:

FATAL EXCEPTION: main 
                   Process: org.ucomplex.ucomplex, PID: 346 
                   java.lang.RuntimeException: Unable to create application org.ucomplex.ucomplex.Common.base.UCApplication: java.lang.RuntimeException: Unable to invoke no-args constructor for ? extends org.ucomplex.ucomplex.Domain.Users.role.Role. Register an InstanceCreator with Gson for this type may fix this problem. 
                    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4729) 
                    at android.app.ActivityThread.access$1600(ActivityThread.java:153) 
                    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412) 
                    at android.os.Handler.dispatchMessage(Handler.java:102) 
                    at android.os.Looper.loop(Looper.java:148) 
                    at android.app.ActivityThread.main(ActivityThread.java:5442) 
                    at java.lang.reflect.Method.invoke(Native Method) 
                    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738) 
                    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628) 
                   Caused by: java.lang.RuntimeException: Unable to invoke no-args constructor for ? extends org.ucomplex.ucomplex.Domain.Users.role.Role. Register an InstanceCreator with Gson for this type may fix this problem. 
                    at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226) 
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210) 
                    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41) 
                    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82) 
                    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61) 
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129) 
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220) 
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129) 
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220) 
                    at com.google.gson.Gson.fromJson(Gson.java:887) 
                    at com.google.gson.Gson.fromJson(Gson.java:952) 
                    at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.deserialize(TreeTypeAdapter.java:162) 
                    at org.ucomplex.ucomplex.Domain.Users.InterfaceAdapter.deserialize(InterfaceAdapter.java:36) 
                    at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:69) 
                    at com.google.gson.Gson.fromJson(Gson.java:887) 
                    at com.google.gson.Gson.fromJson(Gson.java:852) 
                    at com.google.gson.Gson.fromJson(Gson.java:801) 
                    at com.google.gson.Gson.fromJson(Gson.java:773) 
                    at org.ucomplex.ucomplex.Common.FacadePreferences.getUserDataFromPref(FacadePreferences.java:49) 
                    at org.ucomplex.ucomplex.Common.base.UCApplication.onCreate(UCApplication.java:52) 
                    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1014) 
                    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4726) 
                    at android.app.ActivityThread.access$1600(ActivityThread.java:153)  
                    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412)  
                    at android.os.Handler.dispatchMessage(Handler.java:102)  
                    at android.os.Looper.loop(Looper.java:148)  
                    at android.app.ActivityThread.main(ActivityThread.java:5442)  
                    at java.lang.reflect.Method.invoke(Native Method)  
                    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)  
                    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)  
                   Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: org.ucomplex.ucomplex.Domain.Users.role.Role 
                    at com.google.gson.internal.UnsafeAllocator.assertInstantiable(UnsafeAllocator.java:117) 
                    at com.google.gson.internal.UnsafeAllocator.access$000(UnsafeAllocator.java:31) 
                    at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:49) 
                    at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223) 
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)  
                    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)  
                    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)  
                    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)  
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)  
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)  
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)  
                    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)  
                    at com.google.gson.Gson.fromJson(Gson.java:887)  
                    at com.google.gson.Gson.fromJson(Gson.java:952)  
                    at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.deserialize(TreeTypeAdapter.java:162)  
                    at org.ucomplex.ucomplex.Domain.Users.InterfaceAdapter.deserialize(InterfaceAdapter.java:36)  
                    at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:69)  
                    at com.google.gson.Gson.fromJson(Gson.java:887)  
                    at com.google.gson.Gson.fromJson(Gson.java:852)  
                    at com.google.gson.Gson.fromJson(Gson.java:801)  
                    at com.google.gson.Gson.fromJson(Gson.java:773)  
                    at org.ucomplex.ucomplex.Common.FacadePreferences.getUserDataFromPref(FacadePreferences.java:49)  
                    at org.ucomplex.ucomplex.Common.base.UCApplication.onCreate(UCApplication.java:52)  
                    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1014)  
                    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4726)  
                    at android.app.ActivityThread.access$1600(ActivityThread.java:153)  
                    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412)  
                    at android.os.Handler.dispatchMessage(Handler.java:102)  
                    at android.os.Looper.loop(Looper.java:148)  
                    at android.app.ActivityThread.main(ActivityThread.java:5442)  
                    at java.lang.reflect.Method.invoke(Native Method)  
                    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)  
                    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)  

EDIT: Diese Frage als Duplikat markiert wurde. Die markierte Frage beantwortet jedoch die Deserialisierung des Schnittstellentyps (und nicht auf die intelligenteste Weise). Mein Problem ist nicht darin. Ich weiß, wie man den Top-Level-Interface-Typ deserialisiert. In meinem Fall habe ich Base-Implementierung der Schnittstelle, die eine Liste eines anderen Schnittstellentyps hat. Diese Klasse wird für die Komposition verwendet. Picture this:

interface IA {} 

interface IR {} 

class R1 implements IR {} 

class R2 implements IR {} 

class A1 implements IA { 
    private List<IR> list; 
} 

class A2 implements IA { 
    private A1 member; 
} 

class A3 implements IA { 
    private A1 member; 
} 

Problem entsteht, während private List<IR> list; deserilizing.

+0

@chunjef wahr, aber ich weiß nicht wirklich, was ist InstanceCreator und wie man wenden Sie es auf meinen Fall an. Das ist das Problem. – Sermilion

+0

Vielleicht wird dies [http://stackoverflow.com/questions/16396904/using-gson-with-interface-types] helfen. – chunjef

+1

@chunjef diese Frage ist verwandt. Meine Frage geht um eine Stufe anders.Ich habe Interface-Typ, der List und der Fehler ist auf der zweiten Ebene. – Sermilion

Antwort

1

Ich bin mir nicht sicher, ob Sie Ihren Typadapter richtig registriert haben (haben Sie registerTypeHierarchyAdapter oder registerTypeAdapter für jede Klasse verwendet?), Aber Sie stehen vor einem klassischen Problem, wo Sie einen richtigen Klassennamen speichern müssen sogar einen Typnamen einschließlich Generika), um ein Objekt anhand seines konkreten Typs abzurufen. Leider hilft Ihnen InstanceCreator nicht, weil es nur einen bestimmten Typ annehmen kann. RuntimeTypeAdapterFactory, implementiert als Teil von Google Gson Extras, ist entworfen, um mit Typ-Aliase zu arbeiten, so müssten Sie jede bekannte Schnittstelle zuordnen.

Wie auch immer, Sie können es vollständig Schnittstelle-agnostic machen, indem Sie einfach eine passende Adapterfabrik implementieren. Zum Beispiel:

final class InterfaceTypeAdapterFactory 
     implements TypeAdapterFactory { 

    // Effectively a singleton totally holding no state 
    private static final TypeAdapterFactory interfaceTypeAdapterFactory = new InterfaceTypeAdapterFactory(); 

    private InterfaceTypeAdapterFactory() { 
    } 

    // However, let's encapsulate the instantiation 
    static TypeAdapterFactory getInterfaceTypeAdapterFactory() { 
     return interfaceTypeAdapterFactory; 
    } 

    @Override 
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) { 
     // Checking if it's an interface 
     return !typeToken.getRawType().isInterface() 
       // If it's not, then just let Gson pick up a proper type adapter 
       ? null 
       // Otherwise return a null-safe custom type adapter 
       : new InterfaceTypeAdapter<T>(gson).nullSafe(); 
    } 

    private static final class InterfaceTypeAdapter<T> 
      extends TypeAdapter<T> { 

     private static final String TYPE_PROPERTY = "type"; 
     private static final String DATA_PROPERTY = "data"; 

     private final Gson gson; 

     private InterfaceTypeAdapter(final Gson gson) { 
      this.gson = gson; 
     } 

     @Override 
     @SuppressWarnings("resource") 
     public void write(final JsonWriter out, final T value) 
       throws IOException { 
      // Here we're just writing a property value similar to one you did 
      out.beginObject(); 
      out.name(TYPE_PROPERTY); 
      out.value(value.getClass().getName()); 
      out.name(DATA_PROPERTY); 
      gson.toJson(value, value.getClass(), out); 
      out.endObject(); 
     } 

     @Override 
     public T read(final JsonReader in) 
       throws IOException { 
      try { 
       // Deserialization is more complex 
       // Make sure that the current value is an object 
       in.beginObject(); 
       final String name = in.nextName(); 
       final Object value; 
       switch (name) { 
       // If the first property in the stream was type... 
       case TYPE_PROPERTY: 
        final String type = in.nextString(); 
        // Then require the next property to be data 
        if (!in.nextName().equals(DATA_PROPERTY)) { 
         throw new MalformedJsonException("Expected " + DATA_PROPERTY + " at " + in); 
        } 
        // And delegate the deserialization to Gson 
        value = gson.fromJson(in, Class.forName(type)); 
        break; 
       // If some some reason, the order of data and type was messed... 
       case DATA_PROPERTY: 
        // Then store the current value as a JSON tree to deserialize it later 
        // It consumes more memory than the `case TYPE_PROPERTY` case, and you can consider this one as the worst case 
        final JsonElement jsonElement = gson.fromJson(in, JsonElement.class); 
        if (!in.nextName().equals(TYPE_PROPERTY)) { 
         throw new MalformedJsonException("Expected " + TYPE_PROPERTY + " at " + in); 
        } 
        // Restore the value from the tree 
        value = gson.fromJson(jsonElement, Class.forName(in.nextString())); 
        break; 
       default: 
        throw new MalformedJsonException("Unrecognized " + name + " at " + in); 
       } 
       if (in.hasNext()) { 
        throw new IllegalStateException("Unexpected " + in.nextName() + " at " + in); 
       } 
       in.endObject(); 
       @SuppressWarnings("unchecked") 
       final T castValue = (T) value; 
       return castValue; 
      } catch (final ClassNotFoundException ex) { 
       throw new IOException(ex); 
      } 
     } 

    } 

} 

Beispiel:

interface IWhatever { 
} 
final class Wrapper { 

    final IWhatever whatever; 

    Wrapper(final IWhatever whatever) { 
     this.whatever = whatever; 
    } 

} 
final class Foo 
     implements IWhatever { 
} 
final class Bar 
     implements IWhatever { 
} 
private static final Gson gson = new GsonBuilder() 
     .registerTypeAdapterFactory(getInterfaceTypeAdapterFactory()) 
     .create(); 

public static void main(final String... args) { 
    final Wrapper before = new Wrapper(new Foo()); 
    final String json = gson.toJson(before); 
    System.out.println(json); 
    final Wrapper after = gson.fromJson(json, Wrapper.class); 
    System.out.println(after.whatever.getClass()); 
} 

Output:

{"whatever":{"type":"q44005695.Foo","data":{}}}
class q44005695.Foo

Beachten Sie, dass dieses Beispiel nur für Klassen funktionieren kann, wodurch die Typparametrisierung verloren geht. Wenn Sie eine erweiterte Ansatz brauchen Art Parametrisierung für Generika zu halten, können Sie diese verweisen:

+0

Diese Antwort ist reines Gold. Vielen Dank. – Sermilion