Json:使用List of Interface泛型类型反序列化Interface类

时间:2017-05-16 15:31:07

标签: java json gson

我根据用户类型序列化UserInterface类型的子类。那些子类有List。角色也是一个界面。我写了自定义Gson适配器:

public class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {
public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
    final JsonObject wrapper = new JsonObject();
    wrapper.addProperty("type", object.getClass().getName());
    wrapper.add("data", context.serialize(object));
    return wrapper;
}

public T deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
    final JsonObject wrapper = (JsonObject) elem;
    final JsonElement typeName = get(wrapper, "type");
    final JsonElement data = get(wrapper, "data");
    final Type actualType = typeForName(typeName);
    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, String memberName) {
    final JsonElement elem = wrapper.get(memberName);
    if (elem == null) throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");
    return elem;
}
}

问题是,我收到错误:Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: Domain.Users.role.Role。我知道这个问题是什么。 Gson不知道为实例化选择什么具体实现。我如何告诉Gson要实例化哪个类?

这是完整的堆栈跟踪:

FATAL EXCEPTION: main
                                                               Process: org.ucomplex.ucomplex, PID: 346
                                                               java.lang.RuntimeException: Unable to create application org.ucomplex.ucomplex.Common.base.UCApplication: java.lang.RuntimeException: Unable to invoke no-args constructor for ? extends org.ucomplex.ucomplex.Domain.Users.role.Role. Register an InstanceCreator with Gson for this type may fix this problem.
                                                                   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4729)
                                                                   at android.app.ActivityThread.access$1600(ActivityThread.java:153)
                                                                   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412)
                                                                   at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                   at android.os.Looper.loop(Looper.java:148)
                                                                   at android.app.ActivityThread.main(ActivityThread.java:5442)
                                                                   at java.lang.reflect.Method.invoke(Native Method)
                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738)
                                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628)
                                                                Caused by: java.lang.RuntimeException: Unable to invoke no-args constructor for ? extends org.ucomplex.ucomplex.Domain.Users.role.Role. Register an InstanceCreator with Gson for this type may fix this problem.
                                                                   at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
                                                                   at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
                                                                   at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
                                                                   at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
                                                                   at com.google.gson.Gson.fromJson(Gson.java:887)
                                                                   at com.google.gson.Gson.fromJson(Gson.java:952)
                                                                   at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.deserialize(TreeTypeAdapter.java:162)
                                                                   at org.ucomplex.ucomplex.Domain.Users.InterfaceAdapter.deserialize(InterfaceAdapter.java:36)
                                                                   at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:69)
                                                                   at com.google.gson.Gson.fromJson(Gson.java:887)
                                                                   at com.google.gson.Gson.fromJson(Gson.java:852)
                                                                   at com.google.gson.Gson.fromJson(Gson.java:801)
                                                                   at com.google.gson.Gson.fromJson(Gson.java:773)
                                                                   at org.ucomplex.ucomplex.Common.FacadePreferences.getUserDataFromPref(FacadePreferences.java:49)
                                                                   at org.ucomplex.ucomplex.Common.base.UCApplication.onCreate(UCApplication.java:52)
                                                                   at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1014)
                                                                   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4726)
                                                                   at android.app.ActivityThread.access$1600(ActivityThread.java:153) 
                                                                   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412) 
                                                                   at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                   at android.os.Looper.loop(Looper.java:148) 
                                                                   at android.app.ActivityThread.main(ActivityThread.java:5442) 
                                                                   at java.lang.reflect.Method.invoke(Native Method) 
                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738) 
                                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628) 
                                                                Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: org.ucomplex.ucomplex.Domain.Users.role.Role
                                                                   at com.google.gson.internal.UnsafeAllocator.assertInstantiable(UnsafeAllocator.java:117)
                                                                   at com.google.gson.internal.UnsafeAllocator.access$000(UnsafeAllocator.java:31)
                                                                   at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:49)
                                                                   at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223)
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210) 
                                                                   at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41) 
                                                                   at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82) 
                                                                   at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61) 
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129) 
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220) 
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129) 
                                                                   at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220) 
                                                                   at com.google.gson.Gson.fromJson(Gson.java:887) 
                                                                   at com.google.gson.Gson.fromJson(Gson.java:952) 
                                                                   at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.deserialize(TreeTypeAdapter.java:162) 
                                                                   at org.ucomplex.ucomplex.Domain.Users.InterfaceAdapter.deserialize(InterfaceAdapter.java:36) 
                                                                   at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:69) 
                                                                   at com.google.gson.Gson.fromJson(Gson.java:887) 
                                                                   at com.google.gson.Gson.fromJson(Gson.java:852) 
                                                                   at com.google.gson.Gson.fromJson(Gson.java:801) 
                                                                   at com.google.gson.Gson.fromJson(Gson.java:773) 
                                                                   at org.ucomplex.ucomplex.Common.FacadePreferences.getUserDataFromPref(FacadePreferences.java:49) 
                                                                   at org.ucomplex.ucomplex.Common.base.UCApplication.onCreate(UCApplication.java:52) 
                                                                   at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1014) 
                                                                   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4726) 
                                                                   at android.app.ActivityThread.access$1600(ActivityThread.java:153) 
                                                                   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412) 
                                                                   at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                                   at android.os.Looper.loop(Looper.java:148) 
                                                                   at android.app.ActivityThread.main(ActivityThread.java:5442) 
                                                                   at java.lang.reflect.Method.invoke(Native Method) 
                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738) 
                                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628) 

修改 这个问题被标记为重复。但是,标记的问题回答了如何反序列化接口类型(而不是以最聪明的方式)。我的问题不在于此。我知道如何反序列化顶级接口类型。在我的例子中,我有接口的Base实现,它具有另一个接口类型的List。这个类用于组合。 想象一下:

interface IA {}

interface IR {}

class R1 implements IR {}

class R2 implements IR {}

class A1 implements IA {
    private List<IR> list;
}

class A2 implements IA {
    private A1 member;
}

class A3 implements IA {
    private A1 member;
}

在去除private List<IR> list;时会出现问题。

1 个答案:

答案 0 :(得分:1)

我不确定您是否正确注册了类型适配器(您是否为每个班级使用registerTypeHierarchyAdapterregisterTypeAdapter?),但您面临的是经典问题您必须存储适当的类名(甚至包括泛型的类型名称)以通过其具体类型检索对象。不幸的是,InstanceCreator无法帮助您,因为它只能接受某种类型。作为Google Gson附加功能的一部分实现的RuntimeTypeAdapterFactory旨在使用类型别名,因此您必须映射每个已知的接口。

无论如何,只需实现一个正确的类型适配器工厂,就可以使它完全与接口无关。例如:

final class InterfaceTypeAdapterFactory
        implements TypeAdapterFactory {

    // Effectively a singleton totally holding no state
    private static final TypeAdapterFactory interfaceTypeAdapterFactory = new InterfaceTypeAdapterFactory();

    private InterfaceTypeAdapterFactory() {
    }

    // However, let's encapsulate the instantiation
    static TypeAdapterFactory getInterfaceTypeAdapterFactory() {
        return interfaceTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Checking if it's an interface
        return !typeToken.getRawType().isInterface()
                // If it's not, then just let Gson pick up a proper type adapter
                ? null
                // Otherwise return a null-safe custom type adapter
                : new InterfaceTypeAdapter<T>(gson).nullSafe();
    }

    private static final class InterfaceTypeAdapter<T>
            extends TypeAdapter<T> {

        private static final String TYPE_PROPERTY = "type";
        private static final String DATA_PROPERTY = "data";

        private final Gson gson;

        private InterfaceTypeAdapter(final Gson gson) {
            this.gson = gson;
        }

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter out, final T value)
                throws IOException {
            // Here we're just writing a property value similar to one you did
            out.beginObject();
            out.name(TYPE_PROPERTY);
            out.value(value.getClass().getName());
            out.name(DATA_PROPERTY);
            gson.toJson(value, value.getClass(), out);
            out.endObject();
        }

        @Override
        public T read(final JsonReader in)
                throws IOException {
            try {
                // Deserialization is more complex
                // Make sure that the current value is an object
                in.beginObject();
                final String name = in.nextName();
                final Object value;
                switch ( name ) {
                // If the first property in the stream was type...
                case TYPE_PROPERTY:
                    final String type = in.nextString();
                    // Then require the next property to be data
                    if ( !in.nextName().equals(DATA_PROPERTY) ) {
                        throw new MalformedJsonException("Expected " + DATA_PROPERTY + " at " + in);
                    }
                    // And delegate the deserialization to Gson
                    value = gson.fromJson(in, Class.forName(type));
                    break;
                // If some some reason, the order of data and type was messed...
                case DATA_PROPERTY:
                    // Then store the current value as a JSON tree to deserialize it later
                    // It consumes more memory than the `case TYPE_PROPERTY` case, and you can consider this one as the worst case
                    final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
                    if ( !in.nextName().equals(TYPE_PROPERTY) ) {
                        throw new MalformedJsonException("Expected " + TYPE_PROPERTY + " at " + in);
                    }
                    // Restore the value from the tree
                    value = gson.fromJson(jsonElement, Class.forName(in.nextString()));
                    break;
                default:
                    throw new MalformedJsonException("Unrecognized " + name + " at " + in);
                }
                if ( in.hasNext() ) {
                    throw new IllegalStateException("Unexpected " + in.nextName() + " at " + in);
                }
                in.endObject();
                @SuppressWarnings("unchecked")
                final T castValue = (T) value;
                return castValue;
            } catch ( final ClassNotFoundException ex ) {
                throw new IOException(ex);
            }
        }

    }

}

使用示例:

interface IWhatever {
}
final class Wrapper {

    final IWhatever whatever;

    Wrapper(final IWhatever whatever) {
        this.whatever = whatever;
    }

}
final class Foo
        implements IWhatever {
}
final class Bar
        implements IWhatever {
}
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getInterfaceTypeAdapterFactory())
        .create();

public static void main(final String... args) {
    final Wrapper before = new Wrapper(new Foo());
    final String json = gson.toJson(before);
    System.out.println(json);
    final Wrapper after = gson.fromJson(json, Wrapper.class);
    System.out.println(after.whatever.getClass());
}

输出:

  

{&#34;无论&#34; {&#34;类型&#34;:&#34; q44005695.Foo&#34;&#34;数据&#34;:{}}}
  class q44005695.Foo

请注意,此示例仅适用于类,因此会丢失类型参数化。如果您需要更高级的方法来保持泛型的类型参数化,可以参考以下内容: