Gson 不会将值写入使用 IntanceCreator 创建的对象
Gson doesn't write values to objects created with IntanceCreator
我使用 gson 2.8.6,这是我的代码:
interface Foo {
String getTest();
}
class FooImpl implements Foo {
private String test;
@Override
public String getTest() {
return this.test;
}
public void setTest(String test) {
this.test = test;
}
@Override
public String toString() {
return "FooImpl{" + "test=" + test + '}';
}
}
class Bar {
private Foo foo;
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
@Override
public String toString() {
return "Bar{" + "foo=" + foo + '}';
}
}
class FooInstanceCreator implements InstanceCreator<Foo> {
@Override
public Foo createInstance(Type type) {
return new FooImpl();
}
}
public class NewMain3 {
public static void main(String[] args) {
FooImpl foo = new FooImpl();
foo.setTest("this is test");
Bar bar = new Bar();
bar.setFoo(foo);
Gson gson = new GsonBuilder()
.serializeNulls()
.registerTypeAdapter(Foo.class, new FooInstanceCreator())
.create();
//TO
String to = gson.toJson(bar);
System.out.println("TO:" + to);
//FROM
Bar from = gson.fromJson(to, Bar.class);
System.out.println("FROM:" + from);
}
}
这是输出:
TO:{"foo":{"test":"this is test"}}
FROM:Bar{foo=FooImpl{test=null}}
如您所见,test
字段已丢失。谁能告诉我如何解决它?
运行 代码上的调试器,这是反序列化停止的地方 (link)。显然,接口没有绑定字段,所以它只是创建实例然后继续前进而不设置任何东西。
您可以通过以下方式修复它:
final RuntimeTypeAdapterFactory<Foo> typeFactory = RuntimeTypeAdapterFactory
.of(Foo.class, "type")
.registerSubtype(FooImpl.class);
Gson gson = new GsonBuilder()
.serializeNulls()
.registerTypeAdapterFactory(typeFactory)
.create();
其中RuntimeTypeAdapterFactory
顺便可以找到here. I found this in this Whosebug question,隐藏的不是很深。它所做的是,它将在 JSON 对象中写入具体 class 的 class 名称,以便稍后反序列化它:
TO:{"foo":{"type":"FooImpl","test":"this is test"}}
FROM:Bar{foo=FooImpl{test=this is test}}
我没有任何使用 Gson 的经验,但在这个特定方面它似乎不如 Jackson 好。
我认为 InstanceCreator
的想法与 ReflectiveTypeAdapterFactory
的工作原理相矛盾:
- 前者处理 classes,它们实际上是数据包,而不是接口(接口 JavaDoc 中的示例说明了这一点);
- 而后者,根据源代码,预先解析class绑定字段(不是不声明字段的接口) 为
@SerializedName
应用重新映射策略会增加一些复杂性。
这在某种程度上听起来是违反直觉的,但我认为这不是设计问题,而是一个陷阱。你可以在这里做的是创建一个自定义类型适配器,它为它的超类型借用一个子类型类型适配器:
public static <T1, T2 extends T1> TypeAdapterFactory bind(final TypeToken<T1> superTypeToken, final TypeToken<T2> subTypeToken) {
return new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !typeToken.equals(superTypeToken) ) {
return null;
}
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, subTypeToken);
return typeAdapter;
}
};
}
然后在您的 Gson 构建器中注册它:
private static final TypeToken<Foo> fooTypeToken = new TypeToken<Foo>() {};
private static final TypeToken<FooImpl> fooImplTypeToken = new TypeToken<FooImpl>() {};
private static final Gson gson = new GsonBuilder()
.serializeNulls()
.registerTypeAdapterFactory(bind(fooTypeToken, fooImplTypeToken))
.create();
这应该是一次完美的往返旅行。
(Dici 提供的示例是另一个用例:它是一个多态类型适配器,根据指定 JSON 属性 从 JSON 子树反序列化对象要实例化的类型。另一方面,上面的 bind
方法可以被认为是 RuntimeTypeAdapterFactory
的特殊 "typeless" 特化。)
我使用 gson 2.8.6,这是我的代码:
interface Foo {
String getTest();
}
class FooImpl implements Foo {
private String test;
@Override
public String getTest() {
return this.test;
}
public void setTest(String test) {
this.test = test;
}
@Override
public String toString() {
return "FooImpl{" + "test=" + test + '}';
}
}
class Bar {
private Foo foo;
public Foo getFoo() {
return foo;
}
public void setFoo(Foo foo) {
this.foo = foo;
}
@Override
public String toString() {
return "Bar{" + "foo=" + foo + '}';
}
}
class FooInstanceCreator implements InstanceCreator<Foo> {
@Override
public Foo createInstance(Type type) {
return new FooImpl();
}
}
public class NewMain3 {
public static void main(String[] args) {
FooImpl foo = new FooImpl();
foo.setTest("this is test");
Bar bar = new Bar();
bar.setFoo(foo);
Gson gson = new GsonBuilder()
.serializeNulls()
.registerTypeAdapter(Foo.class, new FooInstanceCreator())
.create();
//TO
String to = gson.toJson(bar);
System.out.println("TO:" + to);
//FROM
Bar from = gson.fromJson(to, Bar.class);
System.out.println("FROM:" + from);
}
}
这是输出:
TO:{"foo":{"test":"this is test"}}
FROM:Bar{foo=FooImpl{test=null}}
如您所见,test
字段已丢失。谁能告诉我如何解决它?
运行 代码上的调试器,这是反序列化停止的地方 (link)。显然,接口没有绑定字段,所以它只是创建实例然后继续前进而不设置任何东西。
您可以通过以下方式修复它:
final RuntimeTypeAdapterFactory<Foo> typeFactory = RuntimeTypeAdapterFactory
.of(Foo.class, "type")
.registerSubtype(FooImpl.class);
Gson gson = new GsonBuilder()
.serializeNulls()
.registerTypeAdapterFactory(typeFactory)
.create();
其中RuntimeTypeAdapterFactory
顺便可以找到here. I found this in this Whosebug question,隐藏的不是很深。它所做的是,它将在 JSON 对象中写入具体 class 的 class 名称,以便稍后反序列化它:
TO:{"foo":{"type":"FooImpl","test":"this is test"}}
FROM:Bar{foo=FooImpl{test=this is test}}
我没有任何使用 Gson 的经验,但在这个特定方面它似乎不如 Jackson 好。
我认为 InstanceCreator
的想法与 ReflectiveTypeAdapterFactory
的工作原理相矛盾:
- 前者处理 classes,它们实际上是数据包,而不是接口(接口 JavaDoc 中的示例说明了这一点);
- 而后者,根据源代码,预先解析class绑定字段(不是不声明字段的接口) 为
@SerializedName
应用重新映射策略会增加一些复杂性。
这在某种程度上听起来是违反直觉的,但我认为这不是设计问题,而是一个陷阱。你可以在这里做的是创建一个自定义类型适配器,它为它的超类型借用一个子类型类型适配器:
public static <T1, T2 extends T1> TypeAdapterFactory bind(final TypeToken<T1> superTypeToken, final TypeToken<T2> subTypeToken) {
return new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !typeToken.equals(superTypeToken) ) {
return null;
}
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, subTypeToken);
return typeAdapter;
}
};
}
然后在您的 Gson 构建器中注册它:
private static final TypeToken<Foo> fooTypeToken = new TypeToken<Foo>() {};
private static final TypeToken<FooImpl> fooImplTypeToken = new TypeToken<FooImpl>() {};
private static final Gson gson = new GsonBuilder()
.serializeNulls()
.registerTypeAdapterFactory(bind(fooTypeToken, fooImplTypeToken))
.create();
这应该是一次完美的往返旅行。
(Dici 提供的示例是另一个用例:它是一个多态类型适配器,根据指定 JSON 属性 从 JSON 子树反序列化对象要实例化的类型。另一方面,上面的 bind
方法可以被认为是 RuntimeTypeAdapterFactory
的特殊 "typeless" 特化。)