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" 特化。)