Ok, this is a (working) stub for a solution. It's too late in Italy, to make it nicer.
You need a delegate function like this
package com.google.gson.graph;
/**
* @author Giacomo Tesio
*/
public interface GenericFunction<Domain, Codomain> {
Codomain map(Domain domain);
}
a TypeAdapterFactory like this:
package com.google.gson.graph;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author Giacomo Tesio
*/
public class InterfaceAdapterFactory implements TypeAdapterFactory {
final Map<String, GenericFunction<Gson, TypeAdapter<?>>> adapters;
private final Class<?> commonInterface;
public InterfaceAdapterFactory(Class<?> commonInterface, Class<?>[] concreteClasses)
{
this.commonInterface = commonInterface;
this.adapters = new HashMap<String, GenericFunction<Gson, TypeAdapter<?>>>();
final TypeAdapterFactory me = this;
for(int i = 0; i < concreteClasses.length; ++i)
{
final Class<?> clazz = concreteClasses[i];
this.adapters.put(clazz.getName(), new GenericFunction<Gson, TypeAdapter<?>>(){
public TypeAdapter<?> map(Gson gson) {
TypeToken<?> type = TypeToken.get(clazz);
return gson.getDelegateAdapter(me, type);
}
});
}
}
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
if(!this.commonInterface.isAssignableFrom(type.getRawType())
&& !this.commonInterface.equals(type.getRawType()))
{
return delegate;
}
final TypeToken<T> typeToken = type;
final Gson globalGson = gson;
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
out.beginObject();
out.name("@t");
out.value(value.getClass().getName());
out.name("@v");
delegate.write(out, value);
out.endObject();
}
@SuppressWarnings({"unchecked"})
public T read(JsonReader in) throws IOException {
JsonToken peekToken = in.peek();
if(peekToken == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
String dummy = in.nextName();
String typeName = in.nextString();
dummy = in.nextName();
TypeAdapter<?> specificDelegate = adapters.get(typeName).map(globalGson);
T result = (T)specificDelegate.read(in);
in.endObject();
return result;
}
};
}
}
a pair of tests like these
public final class InterfaceAdapterFactoryTest extends TestCase {
public void testInterfaceSerialization1(){
SampleInterface first = new SampleImplementation1(10);
SampleInterfaceContainer toSerialize = new SampleInterfaceContainer("container", first);
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(SampleInterfaceContainer.class)
.addType(SampleImplementation1.class)
.addType(SampleImplementation2.class)
.registerOn(gsonBuilder);
gsonBuilder.registerTypeAdapterFactory(new InterfaceAdapterFactory(
SampleInterface.class, new Class<?>[] { SampleImplementation1.class, SampleImplementation2.class }
));
Gson gson = gsonBuilder.create();
String json = gson.toJson(toSerialize);
System.out.println(json);
SampleInterfaceContainer deserialized = gson.fromJson(json, SampleInterfaceContainer.class);
assertNotNull(deserialized);
assertEquals(toSerialize.getName(), deserialized.getName());
assertEquals(toSerialize.getContent().getNumber(), deserialized.getContent().getNumber());
}
public void testInterfaceSerialization2(){
SampleImplementation2 first = new SampleImplementation2(5, "test");
SampleInterfaceContainer toSerialize = new SampleInterfaceContainer("container", first);
first.Container = toSerialize;
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(SampleInterfaceContainer.class)
.addType(SampleImplementation1.class)
.addType(SampleImplementation2.class)
.registerOn(gsonBuilder);
gsonBuilder.registerTypeAdapterFactory(new InterfaceAdapterFactory(
SampleInterface.class, new Class<?>[] { SampleImplementation1.class, SampleImplementation2.class }
));
Gson gson = gsonBuilder.create();
String json = gson.toJson(toSerialize);
System.out.println(json);
SampleInterfaceContainer deserialized = gson.fromJson(json, SampleInterfaceContainer.class);
assertNotNull(deserialized);
assertEquals(toSerialize.getName(), deserialized.getName());
assertEquals(5, deserialized.getContent().getNumber());
assertEquals("test", ((SampleImplementation2)deserialized.getContent()).getName());
assertSame(deserialized, ((SampleImplementation2)deserialized.getContent()).Container);
}
}
and some sample classes (to verify that the tests pass)
public class SampleInterfaceContainer {
private SampleInterface content;
private String name;
public SampleInterfaceContainer(String name, SampleInterface content)
{
this.name = name;
this.content = content;
}
public String getName()
{
return this.name;
}
public SampleInterface getContent()
{
return this.content;
}
}
public interface SampleInterface {
int getNumber();
}
public class SampleImplementation1 implements SampleInterface{
private int number;
public SampleImplementation1()
{
this.number = 0;
}
public SampleImplementation1(int number)
{
this.number = number;
}
public int getNumber()
{
return this.number;
}
}
public class SampleImplementation2 implements SampleInterface{
private int number;
private String name;
public SampleInterfaceContainer Container;
public SampleImplementation2()
{
this.number = 0;
this.name = "";
}
public SampleImplementation2(int number, String name)
{
this.number = number;
this.name = name;
}
public int getNumber()
{
return this.number;
}
public String getName()
{
return this.name;
}
}
While this has been a quick&dirty hack, it works like a charme.
You just have to pay attention at the order of the operations during the initialization of GsonBuilder. You have to initialize and register the GraphAdapterBuilder first and only after register this factory.
It has been funny (if a bit tricky since I'm not a Java expert).