Reputation: 423
I'm using GSON to apply a universal serializer to all subclasses of an abstract Base
class. However, GSON will not call my serializer when given actual subclasses of the Base
class unless explicitly told to use Base.class
as a cast. Here's a simple instance of what I'm talking about.
public interface Base<T>{
String getName();
public List<Object> getChildren();
}
public class Derived1 implements Base<Integer>{
private Integer x = 5;
String getName(){
return "Name: " + x;
}
List<Object> getChildren(){
return Lists.newArrayList(new Derived2(), "Some string");
}
}
public class Derived2 implements Base<Double>{
private Double x = 6.3;
String getName(){
return "Name: " + x;
}
List<Object> getChildren(){
return new List<>();
}
}
I'm creating a serializer as follows:
JsonSerializer customAdapter = new JsonSerializer<Base>(){
@Override
JsonElement serialize(Base base, Type sourceType, JsonSerializationContext context){
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("name", base.getName());
JsonArray jsonArray = new JsonArray();
for (Object child : base.getChildren()){
jsonArray.add(context.serialize(child));
}
if (jsonArray.size() != 0){
jsonObject.add("children", jsonArray);
}
}
};
Gson customSerializer = new GsonBuilder()
.registerTypeAdapter(Base.class, customAdapter)
.create();
However, applying my custom serializer to a List
of subclasses does not have the desired effect.
customSerializer.toJson(Lists.newArrayList(new Derived1(), new Derived2()));
This applies the default GSON serialization to my subclasses. Is there any easy way to get my custom serializer to use my custom adapter on all subclasses of the parent class? I suspect that one solution is to use reflection to iterate over all subclasses of Base
and register the custom adapter, but I'd like to avoid something like that if possible.
Note: I don't care about deserialization right now.
Upvotes: 2
Views: 2637
Reputation: 12285
Maybe you should not use JsonSerializer
. Namely, this is possible if you use TypeAdapter
doing the same magic by registering TypeAdapterFactory
that tells Gson
how to serialize any class.
See below TypeAdapterFactory
and TypeAdapter
in it:
public class CustomAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
// If the class that type token represents is a subclass of Base
// then return your special adapter
if(Base.class.isAssignableFrom(typeToken.getRawType())) {
return (TypeAdapter<T>) customTypeAdapter;
}
return null;
}
private TypeAdapter<Base<?>> customTypeAdapter = new TypeAdapter<Base<?>>() {
@Override
public void write(JsonWriter out, Base<?> value) throws IOException {
out.beginObject();
out.value(value.getName());
out.endObject();
}
@Override
public Base<?> read(JsonReader in) throws IOException {
// Deserializing to subclasses not interesting yet.
// Actually it is impossible if the JSON does not contain
// information about the subclass to which to deserialize
return null;
}
};
}
If you do something like this:
@Slf4j
public class SubClassTest {
@Test
public void testIt() {
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapterFactory(new CustomAdapterFactory())
.create();
log.info("\n{}", gson.toJson(new Derived1()));
log.info("\n{}", gson.toJson(new Derived2()));
}
}
the output will be like this:
2018-10-12 23:13:17.037 INFO org.example.gson.subclass.SubClassTest:19 - { "name": "Name: 5" } 2018-10-12 23:13:17.043 INFO org.example.gson.subclass.SubClassTest:20 - { "name": "Name: 6.3" }
If it is not exactly what you want just fix the write(..)
method in the customTypeAdapter
.
Upvotes: 3