I serialize a subclasses of UserInterface type based on type of user. Those subclasses have List. Role is an interface as well. I have wrote custom Gson Adapter:
我根据用户类型序列化UserInterface类型的子类。这些子类清单。角色也是接口。我已经编写了自定义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;
}
}
The problem, however is that I get error: Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: Domain.Users.role.Role
. I know whats the problem. Gson does not know what concrete implementation to choose for instantiation. How can I tell Gson which class to instantiate?
但是,问题是我得到了由:java.lang引起的错误。UnsupportedOperationException:接口不能被实例化!接口名称:Domain.Users.role.Role。我知道有什么问题。Gson不知道要为实例化选择什么具体实现。如何告诉Gson要实例化哪个类?
Here is full stacktrace:
这里充满加:
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)
EDIT: This question got marked as duplicate. However, the marked question answers how to deserialize Interface type (and not in the smartest way). My problem is not in that. I know how to deserialize top level interface type. In my case I have Base implementation of the interface that has a List of another interface type. This class is used for composition. Picture this:
编辑:这个问题被标记为重复。然而,有标记的问题回答了如何反序列化接口类型(而不是以最聪明的方式)。我的问题不在这里。我知道如何反序列化*接口类型。在我的例子中,我有接口的基本实现,它有另一个接口类型的列表。这个类用于写作。想象这样一幅图景:
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;
}
Problem arises while deserilizing private List<IR> list;
.
当遗弃私有列表
1 个解决方案
#1
1
I'm not sure if you have registered your type adapter correctly (did you use registerTypeHierarchyAdapter
or registerTypeAdapter
for every class?), but you're facing with a classic problem where you have to store a proper class name (or even a type name including generics) to retrieve an object by its concrete type. Unfortunately, InstanceCreator
won't help you because it can only accept a certain type. RuntimeTypeAdapterFactory
, implemented as a part of Google Gson extras, is designed to work with type aliases, thus you would have to map each known interface.
我不确定如果你已经注册类型正确的适配器(你使用每个类registerTypeHierarchyAdapter或registerTypeAdapter吗?),但你面对一个经典问题,必须将一个适当的存储类名(甚至类型名称包括泛型)检索对象的具体类型。不幸的是,InstanceCreator不能帮助您,因为它只能接受某个类型。作为谷歌Gson附加组件的一部分实现的RuntimeTypeAdapterFactory设计用于处理类型别名,因此您必须映射每个已知的接口。
Anyway, you can make it fully interface-agnostic by just implementing a proper type adapter factory. For example:
无论如何,只要实现一个合适的类型适配器工厂,就可以使它完全与接口无关。例如:
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);
}
}
}
}
Example of use:
使用的例子:
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());
}
Output:
输出:
{"whatever":{"type":"q44005695.Foo","data":{}}}
class q44005695.Foo{“不管”:{“类型”:“q44005695。Foo”、“数据”类q44005695.Foo:{ } } }
Note that this example can only work for classes thus losing type parameterization. If you need a more advanced approach to keep type parameterization for generics, you can refer these:
注意,此示例只能用于类,从而丢失类型参数化。如果您需要一种更高级的方法来保持泛型的类型参数化,您可以参考以下内容:
- https://ru.*.com/a/605380/13379
- https://ru.*.com/a/605380/13379
- https://ru.*.com/a/605388/13379
- https://ru.*.com/a/605388/13379
#1
1
I'm not sure if you have registered your type adapter correctly (did you use registerTypeHierarchyAdapter
or registerTypeAdapter
for every class?), but you're facing with a classic problem where you have to store a proper class name (or even a type name including generics) to retrieve an object by its concrete type. Unfortunately, InstanceCreator
won't help you because it can only accept a certain type. RuntimeTypeAdapterFactory
, implemented as a part of Google Gson extras, is designed to work with type aliases, thus you would have to map each known interface.
我不确定如果你已经注册类型正确的适配器(你使用每个类registerTypeHierarchyAdapter或registerTypeAdapter吗?),但你面对一个经典问题,必须将一个适当的存储类名(甚至类型名称包括泛型)检索对象的具体类型。不幸的是,InstanceCreator不能帮助您,因为它只能接受某个类型。作为谷歌Gson附加组件的一部分实现的RuntimeTypeAdapterFactory设计用于处理类型别名,因此您必须映射每个已知的接口。
Anyway, you can make it fully interface-agnostic by just implementing a proper type adapter factory. For example:
无论如何,只要实现一个合适的类型适配器工厂,就可以使它完全与接口无关。例如:
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);
}
}
}
}
Example of use:
使用的例子:
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());
}
Output:
输出:
{"whatever":{"type":"q44005695.Foo","data":{}}}
class q44005695.Foo{“不管”:{“类型”:“q44005695。Foo”、“数据”类q44005695.Foo:{ } } }
Note that this example can only work for classes thus losing type parameterization. If you need a more advanced approach to keep type parameterization for generics, you can refer these:
注意,此示例只能用于类,从而丢失类型参数化。如果您需要一种更高级的方法来保持泛型的类型参数化,您可以参考以下内容:
- https://ru.*.com/a/605380/13379
- https://ru.*.com/a/605380/13379
- https://ru.*.com/a/605388/13379
- https://ru.*.com/a/605388/13379