Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Polymorphism work with Gson (Retrofit)

Tags:

Here is my Retrofit instance:

@Provides
@Singleton
ApiManager provideApiManager() {
    RxJava2CallAdapterFactory rxAdapter = RxJava2CallAdapterFactory.create();

    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addNetworkInterceptor(new StethoInterceptor())
            .build();

    Gson gson = new GsonBuilder().create();
    GsonConverterFactory converterFactory = GsonConverterFactory.create(gson);

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(AppConstants.BASE_URL)
            .addConverterFactory(converterFactory)
            .addCallAdapterFactory(rxAdapter)
            .client(okHttpClient)
            .build();
    return retrofit.create(ApiManager.class);
}

Model:

class AbstractMessage {
    String id;
}

class TextMessage extends AbstractMessage {
    String textMessage;
}

class ImageMessage extends AbstractMessage {
    String url;
    String text;
}

Request:

@GET("direct/messages")
Observable<List<AbstractMessage>> getMessages(@Header("Authorization") String authHeader, @Body RequestObject request);   

Executing request:

apiManager.getMessages(authHeader, requestObject)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Consumer<List<AbstractMessage>>() {
        @Override
        public void accept(List<AbstractMessage> messages) throws Exception {
            ...
        }
    });

When I execute a request I receive a collection of AbstractMessage objects. The JSON can contain both text and image messages. In my case JSON converter creates AbstractMessage and maps only the id field. How can I make converter to create TextMessage and ImageMessage objects map all matching fields and then cast it to AbstractMessage. Or there may be some other solution.

like image 551
Alex Nik Avatar asked Nov 06 '17 10:11

Alex Nik


1 Answers

You must create a RuntimeTypeAdapterFactory for the objects AbstractMessage, TextMessage and ImageMessage and then you must set it into the gson instance.

Suppose that you have those objects:

public class Animal {
    protected String name;
    protected String type;

    public Animal(String name, String type) {
        this.name = name;
        this.type = type;
    }
}

public class Dog extends Animal {
    private boolean playsCatch;

    public Dog(String name, boolean playsCatch) {
        super(name, "dog");
        this.playsCatch = playsCatch;
    }
}

public class Cat extends Animal {
    private boolean chasesLaser;

    public Cat(String name, boolean chasesLaser) {
        super(name, "cat");
        this.chasesLaser = chasesLaser;
    }
}

This below is the RuntimeTypeAdapter that you need in order to deserialize (and serialize) correctly those objects:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
    .of(Animal.class, "type")
    .registerSubtype(Dog.class, "dog")
    .registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

The class RuntimeTypeAdapterFactory.java is not shipped with the Gson package, so you have to download it manually.

You can read more about the runtime adapter here and here

Please note that the title of your question should be "Polymorphism with Gson"

I hope it helps.

like image 73
db80 Avatar answered Sep 20 '22 16:09

db80