Custom GSON Serialization with Annotations

I have a very specific case of custom serialization with GSON:

Let's say I have the following class:

public class Student extends BaseModel{
    private int id;
    private String name;
    private Student goodFriend;
    private Student bestFriend;
}

      

BaseModel is just the base class for all my model classes.

When I just do

gson.toJson(student /* Some Student instance */);

      

I will get for example:

{
 id: 1, 
 name: "Jack", 
 goodFriend: {id: 2, name: "Matt"}, 
 bestFriend: {id: 3, name "Tom"}
}

      

This is fine, but I need this:

{
 id: 1, 
 name: "Jack", 
 goodFriend: 2, // only an ID for this field
 bestFriend: {id: 3, name "Tom"} // whole object for this field
 // both fields are of the same Type, so I can't use TypeAdapterFactory for this
}

      

I need to label the fields with a serialization type (id or object) somehow, and then use that labeling to serialize as needed. How to do this in general , not just for the Student class, but for all subclasses of BaseModel?

My only idea was to use custom annotations : describing the fields that I want to serialize as an ID with only one annotation, and the fields that I want to serialize as objects with a different annotation, but I couldn't find a way to get the annotations in the TypeAdapter record method.

Any ideas how to handle this?

+3


source to share


1 answer


I found the answer myself. It turns out there is already an annotation for this kind of case in GSON. It's called @JsonAdapter.

First I had to create a TypeAdapterFactory:

public class BaseModelForeignKeyTypeAdapterFactory implements TypeAdapterFactory {

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if (!BaseModel.class.isAssignableFrom(type.getRawType())) {
            return null;
        }

        TypeAdapter defaultAdapter = gson.getAdapter(type);

        //noinspection unchecked
        return (TypeAdapter<T>) new Adapter(defaultAdapter);
    }

    private static class Adapter<T extends BaseModel> extends TypeAdapter<T> {

        private final TypeAdapter<T> defaultAdapter;

        Adapter(TypeAdapter<T> defaultAdapter) {
            this.defaultAdapter = defaultAdapter;
        }

        @Override
        public void write(JsonWriter out, T value) throws IOException {
            out.value(value.getId());
        }

        @Override
        public T read(JsonReader in) throws IOException {
            return defaultAdapter.read(in);
        }
    }
}

      

In the create () method, I retrieve the default adapter that Gson used for this field and passed it to the adapter to use when deserializing the field. Therefore, this adapter is used only for serialization, and deserialization is delegated to the default adapter.

Now I just need to annotate the fields in my Student class that I want to serialize as IDs with this TypeAdapterFactory like this:



public class Student extends BaseModel{
    private int id;
    private String name;

    @JsonAdapter(BaseModelForeignKeyTypeAdapterFactory.class)
    private Student goodFriend;

    private Student bestFriend;
}

      

And that's all, now it gson.toJson(student)

will output:

{
 id: 1, 
 name: "Jack", 
 goodFriend: 2, // using "ForeignKey" TypeAdapter
 bestFriend: {id: 3, name "Tom"} // using default TypeAdapter
}

      

Hope this helps someone!

+5


source







All Articles