2016-06-28 15 views
5

I Retrofit 2.1.0 mit Konverter-Gson bin mit: 2.1.0 und separat Gson: 2.6.2 um die Serialisierung/Deserialisierung anpassen. Das Problem ist, dass meine POJOs hinter Schnittstellen versteckt sein sollten und ich gson mitteilen möchte, welche Klasse die deserialisierte Schnittstelle sein soll. Und nach der Deserialisierung/Serialisierung sollte Retrofit die Schnittstelle zurückgeben können. Es wäre gut, wenn ich die Vorteile von Generics nutzen und leicht eine Möglichkeit schaffen könnte, Gson oder Retrofit zu sagen, FooInterface zu FooClass zu serialisieren/deserialisieren.Gson deserialisieren Schnittstelle zu seiner Klasse Implementierung

Antwort

11

Ich gehe davon aus, dass Sie einen einzigen Deserializer für alle Ihre Schnittstellen und ihre jeweiligen Implementierungen erstellen möchten. Befolgen Sie diese Schritte bitte:

1. Erstellen Sie eine Basisschnittstelle, die von Ihren anderen App-Schnittstellen erweitert wird. Es ist erforderlich, einen einzelnen Deserializer für alle Ihre Schnittstellen und Implementierungsklassen zu erstellen.

public interface Convertable { 
    String getClassName(); 
} 

2. Erstellen Sie Ihre Feature-Schnittstelle und Implementierungsklasse. Als Beispiel nennen wir sie FooInterface und FooClass. FooInterface sollte Convertierbare Schnittstelle erweitern.

FooInterface

public interface FooInterface extends Convertable { 

} 

FooClass

public class FooClass implements FooInterface { 

    // DISCRIMINATOR FIELD 
    private final String className; 

    private String field1; 

    private String field2; 

    public FooClass() { 
     this.className = getClass().getName(); 
    } 

    public String getClassName() { 
     return className; 
    } 

    public String getField1() { 
     return field1; 
    } 

    public void setField1(String field1) { 
     this.field1 = field1; 
    } 

    public String getField2() { 
     return field2; 
    } 

    public void setField2(String field2) { 
     this.field2 = field2; 
    } 

} 

anzumerken, dass der zurückgegebene Wert von getClassName() als Diskriminator Feld verwendet wird, die in Gson Deserializer (nächster Schritt) verwendet wird, um initialisiere Mehrweginstanz. Ich gehe davon aus, dass Ihre Serializer- und Deserializer-Klasse im selben Paket enthalten ist, auch wenn sie sich in verschiedenen Client- und Serveranwendungen befinden. Wenn nicht, dann müssen Sie die getClassInstance() - Implementierung ändern, aber das wäre ziemlich einfach.

3. Implementieren eines benutzerdefinierten Gson Serializer für alle Anwendungs ​​

import com.google.gson.JsonDeserializationContext; 
import com.google.gson.JsonDeserializer; 
import com.google.gson.JsonElement; 
import com.google.gson.JsonObject; 
import com.google.gson.JsonParseException; 
import com.google.gson.JsonPrimitive; 

public class ConvertableDeserializer<T extends Convertable> implements JsonDeserializer<T> { 

    private static final String CLASSNAME = "className"; 

    public T deserialize(final JsonElement jsonElement, final Type type, 
         final JsonDeserializationContext deserializationContext 
         ) throws JsonParseException { 

     final JsonObject jsonObject = jsonElement.getAsJsonObject(); 
     final JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); 
     final String className = prim.getAsString(); 
     final Class<T> clazz = getClassInstance(className); 
     return deserializationContext.deserialize(jsonObject, clazz); 
    } 

    @SuppressWarnings("unchecked") 
    public Class<T> getClassInstance(String className) { 
     try { 
      return (Class<T>) Class.forName(className); 
     } catch (ClassNotFoundException cnfe) { 
      throw new JsonParseException(cnfe.getMessage()); 
     } 
    } 

} 

4. Register Deserializer mit Gson und initialisieren Retrofit

private static GsonConverterFactory buildGsonConverter() { 

     final GsonBuilder builder = new GsonBuilder(); 

     // Adding custom deserializers 
     builder.registerTypeAdapter(FooInterface.class, 
            new ConvertableDeserializer<FooInterface>()); 
     final Gson gson = builder.create(); 

     return GsonConverterFactory.create(myGson); 
    } 


    public void initRetrofit() { 
     Retrofit retrofit = new Retrofit.Builder() 
       .baseUrl("REST_ENDPOINT") 
       .addConverterFactory(buildGsonConverter()) 
       .client(httpClient) 
       .build(); 
    } 

Sie den Adapter für alle registrieren kann Ihrer Implementierungen, wenn Sie möchten, mit:

+0

Ich werde diese Implementierung überprüfen und berichten. – toshkinl

+0

Hast du es geschafft, es zu überprüfen? –

+0

Ich erhalte "NPE" in der Zeile "final String className = prim.getAsString();' der Klasse 'ConvertibleDeserializer'. Und das ist offensichtlich. Mein JSON hat kein 'className'-Feld darin. – IlyaEremin

3

Weil Sie bereit sind, sich die Mühe nehmen fast die gesamte Domain Ebene zu duplizieren Schnittstellen mit der Implementierung Detail Ihrer Modelle zu verbergen, ich glaube, Sie meine Antwort erfrischende finden;)

Sie sollten AutoValue verwenden, um um Implementierungsdetails in Ihren Modellen zu verbergen. Die Art, wie es funktioniert, ist ziemlich einfach:

Sie schreiben eine abstrakte Klasse, und AutoValue implementiert es. Das ist alles da ist es; Es gibt buchstäblich keine Konfiguration.

Mit dieser Methode müssen Sie keine solche Menge an Boilerplate erstellen.

Und es gibt diese AutoValue-Erweiterung namens auto-value-gson, die Gson De/Serializer-Unterstützung aus der Box hinzugefügt.

Mit diesen einfachen Schritten denke ich, dass Ihre Code-Basis wesentlich verbessert wird.

+0

Können Sie ein Beispiel geben? –

Verwandte Themen