【问题标题】:gson - deserialzing objects containing listsgson - 反序列化包含列表的对象
【发布时间】:2017-01-27 10:12:49
【问题描述】:

我是 gson 新手,正在尝试研究如何反序列化对象中的列表。

错误消息提示为 Player 创建一个 InstanceCreator,我这样做了。 但是在实现时,我发现反序列化的对象包含一个玩家列表,其中 name 字段设置为“默认值”,而不是从 json 字符串中获取值。所以我现在想知道这种方法是否正确。

这是对我正在研究的模型的简化,但突出了问题...

public interface Player {
    String name();
}

public class PlayerImpl implements Player {

    private String name;

    public PlayerImpl(String name) {
        this.name = name;
    }

    public String name() { return this.name; }
}

public interface Team {
...
}

public class TeamImpl implements Team {

    String name;
    List<Player> players = new ArrayList<>();

    public TeamImpl(String teamName) { this.name = teamName; }

    ...
}

我有一个简单的测试来创建一个有 2 名玩家的新团队

Team t = new TeamImpl("teamname");
t.addPlayer(new PlayerImpl("p1"));
t.addPlayer(new PlayerImpl("p2"));
Gson gson = new Gson();
String json = gson.toJson(t);    

创建以下 json 字符串:

{"name":"teamname","players":[{"name":"p1"},{"name":"p2"}]}

但是,当我反序列化 json 字符串时...

Team t2 = gson.fromJson(json, TeamImpl.class);

我收到以下错误:

java.lang.RuntimeException: Unable to invoke no-args constructor for interface com.example.Player. 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.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 com.example.data.JsonDataTests.test_team_json(JsonDataTests.java:62)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
    at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
    at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
    at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
    at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:71)
    at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
    at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
    at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:121)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: com.example.Player
    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)
    ... 35 more

【问题讨论】:

  • 尝试列表 player = new ArrayList();而不是 List 玩家 = new ArrayList();在 TeamImpl 因为是错误接口不能被实例化!接口名称:com.example.Player

标签: java gson json-deserialization


【解决方案1】:

Gson 明确报告它无法实例化接口。为了使其成为可能,您可以注册一个自定义类型适配器,该适配器知道如何序列化实例并将其反序列化。

final class InterfaceSerializer<T>
        implements JsonSerializer<T>, JsonDeserializer<T> {

    private final Class<T> implementationClass;

    private InterfaceSerializer(final Class<T> implementationClass) {
        this.implementationClass = implementationClass;
    }

    static <T> InterfaceSerializer<T> interfaceSerializer(final Class<T> implementationClass) {
        return new InterfaceSerializer<>(implementationClass);
    }

    @Override
    public JsonElement serialize(final T value, final Type type, final JsonSerializationContext context) {
        final Type targetType = value != null 
                ? value.getClass() // `type` can be an interface so Gson would not even try to traverse the fields, just pick the implementation class 
                : type;            // if not, then delegate further
        return context.serialize(value, targetType);
    }

    @Override
    public T deserialize(final JsonElement jsonElement, final Type typeOfT, final JsonDeserializationContext context) {
        return context.deserialize(jsonElement, implementationClass);
    }

}

然后配置您的 Gson 实例(每个应用程序一次,Gson 是不可变且线程安全的):

Gson gson = new GsonBuilder()
        .registerTypeAdapter(Player.class, interfaceSerializer(PlayerImpl.class))
        .registerTypeAdapter(Team.class, interfaceSerializer(TeamImpl.class))
        .create();

请注意,现在即使Team t2 = gson.fromJson(json, Team.class); 也可以工作(没有TeamImpl.class

也许是基于意见的,但是...我强烈建议您不要绑定到 Gson 中的接口。 Gson 的唯一职责是对您的应用程序使用的业务/价值对象进行序列化和反序列化。查看Data Transfer Object 模式,该模式描述了问题并建议对将数据传入和传出应用程序的对象进行传输。有了这个,你可以从你的 DTO 中删除接口,这样你就不需要这样的适配器,也不需要关心 DTO 类是如何声明和注释的。知道您可能拥有TeamDtoPlayerDto,它们甚至无法实现您的接口,但可以清楚地绑定数据,例如class TeamDto { private List&lt;PlayerDto&gt; }。 DTO 可以很容易地转换为简单的实现,也可以很容易地从它们构建。

【讨论】:

    【解决方案2】:

    Gson 在反序列化期间使用 POJO 而不是接口。请使用实现PlayerImpl 而不是接口Player。还有像 Genson 这样的其他库支持您的需求。

    【讨论】:

      【解决方案3】:

      你有这个错误:Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: com.example.Player 所以你应该在TeamImpl 中创建一个列表,像这样 `List player = new ArrayList();因为接口不能在 GSON 中实例化。

      public interface Player {
       String name();
      }
      public class PlayerImpl implements Player {
      
      private String name;
      
      public PlayerImpl(String name) {
          this.name = name;
      }
      
      public String name() { return this.name; }
      }
      
      
      public interface Team {
      
         void addPlayer(PlayerImpl p1);
      }
      
      
      import java.util.ArrayList;
      import java.util.List;
      
      public class TeamImpl implements Team {
      
      String name;
      List<PlayerImpl> players = new ArrayList<PlayerImpl>();
      
      public TeamImpl(String teamName) { this.name = teamName; }
      
      
      @Override
      public void addPlayer(PlayerImpl p1) {
          this.players.add(p1);
      }
      }
      
      And the main to test it:
      public static void main(String[] args) {
      
      Team t = new TeamImpl("teamname");
      t.addPlayer(new PlayerImpl("p1"));
      t.addPlayer(new PlayerImpl("p2"));
      Gson gson = new Gson();
      String json = gson.toJson(t);
      
      System.out.println(json);
      
      Team t2 = gson.fromJson(json, TeamImpl.class);
      System.out.println(t2.toString());
      
      }
      

      `

      【讨论】:

        【解决方案4】:

        一般情况下,当类使用接口作为字段时,也会导致此异常。尤其是在创建树结构时,很难看出你做错了什么:

        class A implements IA{
           IA parent;
           Collection<IA> nodes;
        }
        
        IA myimpl = gson.fromJson( data_str, A.class );
        

        其中IA是A类的接口。这段代码将抛出上述异常。

        【讨论】:

          猜你喜欢
          • 2016-09-27
          • 2015-08-17
          • 1970-01-01
          • 2016-10-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-06
          相关资源
          最近更新 更多