GSON GraphAdapterBuilder не работает с интерфейсами

Я пытаюсь использовать GraphAdapterBuilder, который является дополнением к библиотеке GSON, для сериализации объекта с циклическими ссылками. Он отлично работает для класса, но не работает при попытке десериализации интерфейса.

Для десериализации интерфейса (который GSON не делает по умолчанию) я использую PropertyBasedInterfaceMarshal или InterfaceAdapter. Они зарегистрированы в качестве пользовательских адаптеров типа для интерфейсов.

При использовании вышеупомянутого эфира оба не могут десериализовать интерфейс, так как им передается только идентификатор графа, например "0x4", генерируемый GraphAdapterBuilder. Это передается как JsonElement в десериализаторе. Очевидно, что с этим идентификатором ничего нельзя сделать из десериализатора.

Разве они не должны быть перехвачены GraphAdapterBuilder вместо попытки десериализации? Я не смог обойти это, это ошибка с GraphAdapterBuilder или есть способ обойти это?

1 ответ

Решение

Хорошо, это (рабочая) заглушка для решения. Слишком поздно в Италии, чтобы сделать его лучше.

Вам нужна функция делегата, как это

package com.google.gson.graph;

/**
 * @author Giacomo Tesio
 */
public interface GenericFunction<Domain, Codomain> {
    Codomain map(Domain domain);
}

TypeAdapterFactory, как это:

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;
           }
        };
    }

}

пара таких тестов

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);
    }
}

и некоторые примеры классов (чтобы убедиться, что тесты пройдены)

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;
    }
}

Хотя это был быстрый и грязный хак, он работает как чудо.

Вам просто нужно обратить внимание на порядок операций при инициализации GsonBuilder. Вы должны инициализировать и зарегистрировать GraphAdapterBuilder сначала и только после регистрации этой фабрики.

Это было забавно (хотя и немного сложно, так как я не эксперт по Java).

Другие вопросы по тегам