我有一个这样的课:
public class A {
@serializedName("type")
Class<?> type;
...
}
但是,当我尝试对其进行序列化时,出现一条错误消息:“试图序列化java.lang.class:java.lang.String。忘记注册TypeAdapter吗?”。所以我创建了这个适配器:
public class MyTypeAdapter extends TypeAdapter<Class> {
public Class read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
} else {
String className = in.nextString();
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new JsonParseException("class " + className + " not found");
}
}
}
public void write(JsonWriter out, Class value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.getName());
}
}
}
并这样注册:
new GsonBuilder().registerTypeAdapter(Class.class, new MyTypeAdapter ()).create().
fromJson(value, listType);
但是我仍然遇到同样的错误。
我究竟做错了什么?
适配器的实现看起来可以吗?
最佳答案
我究竟做错了什么?
Gson考虑了类型信息:您正在尝试混合Class
和Class<?>
,它们是分别代表原始类型和通配符参数化类型的不同类型。
从这个角度来看,Gson不考虑Class
(在registerTypeAdapter
中找到)和Class<?>
(在DTO中找到)等效。
对于这种情况,您必须向registerTypeHierarchyAdapter
注册类型层次结构适配器。
适配器的实现看起来可以吗?
是的,但是可以稍作改进:
final class ClassTypeAdapter
extends TypeAdapter<Class<?>> {
// The type adapter does not hold state, so it can be easily made singleton (+ making the constructor private)
private static final TypeAdapter<Class<?>> instance = new ClassTypeAdapter()
// This is a convenient method that can do trivial null-checks in write(...)/read(...) itself
.nullSafe();
private ClassTypeAdapter() {
}
static TypeAdapter<Class<?>> get() {
return instance;
}
@Override
public void write(final JsonWriter out, final Class<?> value)
throws IOException {
// value is never a null here
out.value(value.getName());
}
@Override
public Class<?> read(final JsonReader in)
throws IOException {
try {
// This will never be a null since nullSafe() is used above
final String className = in.nextString();
return Class.forName(className);
} catch ( final ClassNotFoundException ex ) {
// No need to duplicate the message generated in ClassNotFoundException
throw new JsonParseException(ex);
}
}
}
由于它是said的@Daniel Pryden,因此这可能是一个巨大的安全问题,因为
Class.forName
可能会执行代码(静态初始化程序)。在执行
className
之前,应对照类白名单检查Class.forName(...)
。另请注意,
Class
实例不保存类型参数化(请参阅TypeToken
和ParameterizedType
的含义),并且您可能希望使用所有类型参数化对类型进行编码(虽然toString(...)
很容易,但解析起来并不那么容易-我面对这样的问题,并通过在JParsec中实现解析器解决了)。