Gson deserializes an interface to implement a class

I am using Retrofit 2.1.0 with converter-gson: 2.1.0 and separately gson: 2.6.2 to configure serialization / deserialization. The problem is that my POJOs should be hidden behind the interfaces, and I want to tell Gson which class should be the deserialized interface. And after deserialization / serialization, Retrofit should be able to return the interface. It would be nice if I could take advantage of Generics and easily create a way to tell Gson or Retrofit about FooInterface serialization / deserialization in FooClass.

+6
source share
2 answers

I assume that you want to create one deserializer for all your interfaces and their respective implementations. Follow these steps:

1. Create a basic interface that will be expanded by your other application interfaces. It is required to create a single deserializer for all your interfaces and implementation classes.

public interface Convertable { String getClassName(); } 

2. Create your own function interface and implementation class. As an example, let's name them FooInterface and FooClass. FooInterface should extend the Convertable interface.

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

Note that the value returned by getClassName () is used as the discriminator field that will be used in the Gson Deserializer (next step) to initialize the returned instance. I assume that your serializer and the deserializer class will be in the same package, even if they are in different client and server applications. If not, you will need to change the implementation of getClassInstance (), but it will be quite simple to do so.

3. Deploy a custom Gson serializer for your entire application.

 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 the deserializer with Gson and initialize the modification

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

You can register an adapter for all your implementations, if you want, using:

 builder.registerTypeAdapter(Convertable.class, new ConvertableDeserializer<Convertable>()); 
+11
source

Since you are ready to make an effort to almost duplicate your entire domain level using interfaces to hide the details of the implementation of your models, I think you will find my answer again;)

You must use AutoValue to hide implementation details in your models. The principle of operation is quite simple:

You are writing an abstract class, and AutoValue implements it. That's all there is; literally no configuration.

Taking this approach, you do not need to create so many templates.

And there is this AutoValue extension called auto-value-gson that adds Gson De / Serializer support out of the box.

With these simple steps, I think your code base will improve significantly.

+3
source

All Articles