Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Gson and Abstract Classes

I'm trying to exchange messages between a client and a server using GSON.

The problem is the following:

I have this structure:

public class Message 
{
    private TypeOfContent type; //  It's a enum 
    private Content       content;
    ....
}

Then the object content can be a various set of Classes.

I found 2 tutorials here and here, but none of them solves the problem.

Edit1:

The class Message is this one:


public class Mensagem
{
    private TipoMensagem    type;

    private Conteudo        conteudo;

    private Cliente         autor;
    private Cliente         destino;    // null -> to all(broadcast)
}

And Content is this one:

public class Conteudo
{
    protected   TipoConteudo typeConteudo; 

    protected   String      texto;

    protected   Posicao     posicao;

    public Conteudo(TipoConteudo typeConteudo, String texto, Posicao posicao)
    {
        this.texto   = texto;
        this.posicao = posicao;  
        this.typeConteudo = typeConteudo;
    }    
} 

And an example of a extend class from conteudo is this one:

public class ConteudoTweet extends Conteudo 
{
    protected   String      pathImagem;

    public ConteudoTweet(TipoConteudo typeConteudo, String tweet, Posicao location, String picturePath) 
    {
        super(typeConteudo,tweet, location);
    
        this.pathImagem = picturePath;
    }
}

Finally what I do is like : "String strObject = new Gson().toJson(mensage);" which works but on deserialization it doesn't because it assumes always that it is from Content class

like image 837
André Carvalho Avatar asked Apr 14 '13 14:04

André Carvalho


People also ask

Is GSON better than JSON?

GSON can use the Object definition to directly create an object of the desired type. While JSONObject needs to be parsed manually.

Does GSON ignore transient fields?

By default, GSON excludes transient and static fields from the serialization/deserialization process.

Why do we use GSON in Java?

Gson Goals: Provide simple toJson() and fromJson() methods to convert Java objects to JSON and vice-versa. Allow pre-existing unmodifiable objects to be converted to and from JSON. Extensive support of Java Generics.

Does GSON use constructor?

Gson requires the class to have a default no-args constructor.


2 Answers

I finally solved it!

    // GSON

    GsonBuilder gsonBilder = new GsonBuilder();
    gsonBilder.registerTypeAdapter(Conteudo.class, new InterfaceAdapter<Conteudo>());
    gsonBilder.setPrettyPrinting();

    Gson gson =gsonBilder.create();

    String str2send = gson.toJson(message);

    Mensagem msg_recv = gson.fromJson(str2send,Mensagem.class);

Note that: "registerTypeAdapter(AbstractClass.class, new InterfaceAdapter());"

by AbstractClass.class i mean the class that you are implementing in my case it was Conteúdo that could be ConteudoTweet or ConteudoUserSystem and so on...

The implementation of InterfaceAdapter is :

import java.lang.reflect.Type;

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.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class InterfaceAdapter<T>
    implements JsonSerializer<T>, JsonDeserializer<T> {

    @Override
    public final JsonElement serialize(final T object, final Type interfaceType, final JsonSerializationContext context) 
    {
        final JsonObject member = new JsonObject();

        member.addProperty("type", object.getClass().getName());

        member.add("data", context.serialize(object));

        return member;
    }

    @Override
    public final T deserialize(final JsonElement elem, final Type interfaceType, final JsonDeserializationContext context) 
            throws JsonParseException 
    {
        final JsonObject member = (JsonObject) elem;
        final JsonElement typeString = get(member, "type");
        final JsonElement data = get(member, "data");
        final Type actualType = typeForName(typeString);

        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, final String memberName) 
    {
        final JsonElement elem = wrapper.get(memberName);

        if (elem == null) 
        {
            throw new JsonParseException(
                "no '" + memberName + "' member found in json file.");
        }
        return elem;
    }

}

And this InterfaceAdapter is generic so it should work in general...

That's it!

like image 198
André Carvalho Avatar answered Sep 22 '22 11:09

André Carvalho


You should take a look on a similar question I've answered here : https://stackoverflow.com/a/22081826/3315914

You need to use Gson's RuntimeTypeAdapterFactory

And register the base class and all child classes to make it work.

like image 27
rpax Avatar answered Sep 19 '22 11:09

rpax