使用Gson 2.3.1反序列化包含不在Java中工作的接口的Json String

时间:2015-04-20 05:03:16

标签: java json gson deserialization

我试图将包含接口类型的接口和hashmaps的json字符串反序列化,并使用Gson将包含接口类型的列表反序列化到java对象中。但我得到了

java.lang.RuntimeException:无法为接口com.abc.Dummy调用no-args构造函数。使用Gson为此类型注册InstanceCreator可以解决此问题。

我尝试通过实例化构造函数来为实例创建者注册类型适配器,以实现具有虚拟值的类,但虚拟值不会被反序列化值覆盖?

Dummy是接口,SubClassDummy是实现类。

public class DummyInstanceCreator implements InstanceCreator<Dummy>{
    @Override
    public SubClassDummy createInstance(Type type) {
        return new SubClassDummy("", 2.5, "abc");
    }
}

String data = // some json string with interfaces and List<interface.class>, hashmap<int, interface.class>
gsonBuilder.registerTypeAdapter(Dummy.class, new DummyInstanceCreator());
SubClassDummy context = gson.fromJson(data, SubClassDummy.class);

任何人都可以帮忙解决这个问题吗?我无法修改我试图反序列化的java对象。 它是第三方。因此无法添加任何注释或对该类进行任何修改。如何让gson用反序列化的值覆盖虚拟值? 我甚至无法对java对象如何序列化为json字符串进行任何更改。我只是将json字符串用于将其反序列化为特定的java对象。

3 个答案:

答案 0 :(得分:0)

  

java.lang.RuntimeException:无法为接口com.abc.Dummy调用no-args构造函数

您的Dummy需要是一个类(不是接口)并重新查询默认/空的无参数构造函数:

public class Dummy {

    public Dummy (){
    }

    [...] // your code
}

一些解释:

Json解析器以这种方式创建Dummy的实例:

Dummy instance = Dummy.class.newInstance(); // calls default constructor

然后将初始化使用反射API instance。因此,如果您的类没有实现默认构造函数Dummy.class.newInstance()将失败。

答案 1 :(得分:0)

按照OP的问题,我在下面编写了JUnit测试。它显示了事物在原理上应该如何工作。

但是:由于界面需要使用getter / setter,因此无法立即使用...

问题https://github.com/google/gson/issues/232仍然打开,因此失败...

另请参见Why does GSON use fields and not getters/setters?https://sites.google.com/site/gson/gson-design-document:“ 我们打算在以后的版本中增强Gson,以支持作为表示Json字段的替代映射的属性。目前,Gson基于字段。”-截至2018年,这仍然是一个意图。

您可以尝试https://stackoverflow.com/a/16400945/1497139

中描述的解决方法

对于此用例,最好使用其他JSON库 例如杰克逊(Jackson)或JaxB,例如EclipseLink Moxy。

我为FastJson创建了下面的测试,该测试失败了。

gson的JUnit测试

package com.bitplan.rest.jqgrid;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;

public class TestGson {
  public boolean debug = true;

  interface Dummy {
    String getName();

    void setName(String name);
  }

  class SubClassDummy implements Dummy {

    String name;

    /**
     * non args constructor to make gson happy
     */
    public SubClassDummy() {

    }

    public SubClassDummy(String pName) {
      this.name = pName;
    }

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

  }

  public class DummyInstanceCreator implements InstanceCreator<Dummy> {

    @Override
    public Dummy createInstance(Type type) {
      return new SubClassDummy();
    }

  }

  class DataContainer {
    List<Dummy> names = new ArrayList<Dummy>();
    Map<Integer, Dummy> namesByNumber = new HashMap<Integer, Dummy>();

    public DataContainer(String... pNames) {
      for (String name : pNames) {
        SubClassDummy scd = new SubClassDummy(name);
        names.add(scd);
        namesByNumber.put(names.size(), scd);
      }
    }
  }

  @Test
  public void testGson() {
    GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting();
    Gson gson = gsonBuilder.create();
    DataContainer dc = new DataContainer("John Doe", "Mary Brown", "Tim Smith");
    // some json string with interfaces and
    // List<interface.class>, hashmap<int, interface.class>
    String data = gson.toJson(dc);
    if (debug)
      System.out.println(data);
    String expected = "{\n" + "  \"names\": [\n" + "    {\n"
        + "      \"name\": \"John Doe\"\n" + "    },\n" + "    {\n"
        + "      \"name\": \"Mary Brown\"\n" + "    },\n" + "    {\n"
        + "      \"name\": \"Tim Smith\"\n" + "    }\n" + "  ],\n"
        + "  \"namesByNumber\": {\n" + "    \"1\": {\n"
        + "      \"name\": \"John Doe\"\n" + "    },\n" + "    \"2\": {\n"
        + "      \"name\": \"Mary Brown\"\n" + "    },\n" + "    \"3\": {\n"
        + "      \"name\": \"Tim Smith\"\n" + "    }\n" + "  }\n" + "}";
    assertEquals(expected, data);

    try {
      DataContainer dc2 = gson.fromJson(data, DataContainer.class);
      assertNotNull(dc2);
      fail("This can't happen - according to https://stackoverflow.com/questions/29739648/deserializing-json-string-containing-interfaces-not-working-in-java-using-gson-2 there should be an exception");
    } catch (RuntimeException re) {
      assertTrue(re.getMessage().contains("no-args constructor for interface"));
    }
    gsonBuilder.registerTypeAdapter(Dummy.class, new DummyInstanceCreator());
    Gson gson2 = gsonBuilder.setPrettyPrinting().create();
    DataContainer dc3 = gson2.fromJson(data, DataContainer.class);
    assertNotNull(dc3);
    String data2=gson2.toJson(dc3);
    // issue https://github.com/google/gson/issues/232 is still open so this fails ...
    assertEquals(data,data2);

  }

}

使用的Maven依赖项:

   <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
    </dependency>

fastjson的JUnit测试

package com.bitplan.rest.jqgrid;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;

import com.alibaba.fastjson.JSON;
import com.bitplan.rest.jqgrid.TestGson.DataContainer;

/**
 * test Alibaba FastJson
 * 
 * @author wf
 *
 */
public class TestJson {
  public boolean debug = true;

  @Test
  public void testJson() {
    DataContainer dc = new DataContainer("John Doe", "Mary Brown", "Tim Smith");
    // some json string with interfaces and
    // List<interface.class>, hashmap<int, interface.class>
    // String data = gson.toJson(dc);
    String data = JSON.toJSONString(dc);
    if (debug)
      System.out.println(data);
    String expected = "{\n" + "  \"names\": [\n" + "    {\n"
        + "      \"name\": \"John Doe\"\n" + "    },\n" + "    {\n"
        + "      \"name\": \"Mary Brown\"\n" + "    },\n" + "    {\n"
        + "      \"name\": \"Tim Smith\"\n" + "    }\n" + "  ],\n"
        + "  \"namesByNumber\": {\n" + "    \"1\": {\n"
        + "      \"name\": \"John Doe\"\n" + "    },\n" + "    \"2\": {\n"
        + "      \"name\": \"Mary Brown\"\n" + "    },\n" + "    \"3\": {\n"
        + "      \"name\": \"Tim Smith\"\n" + "    }\n" + "  }\n" + "}";
    assertEquals(expected, data);

    DataContainer dc2 = JSON.parseObject(data, DataContainer.class);
    assertNotNull(dc2);
    DataContainer dc3 = JSON.parseObject(data, DataContainer.class);
    assertNotNull(dc3);
    String data2 = JSON.toJSONString(dc3);
    assertEquals(data, data2);
  }

}

使用的Maven依赖项:

  <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.54</version>
    </dependency>

答案 2 :(得分:0)

要反序列化接口,这对我来说是gson 2.8.5的窍门- 您还需要定义serialize()

 public class DummyInstanceDeserializer implements JsonDeserializer<Dummy>,JsonSerializer<Dummy>
  {
   @Override
      public Dummy deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
          return context.deserialize(json, SubClassDummy.class);
      }
    @Override
    public JsonElement serialize(Dummy src, Type typeOfSrc, JsonSerializationContext context) {
        return context.serialize(src);
    }
  }

...

 gsonBuilder.registerTypeAdapter(Dummy.class, new DummyInstanceDeserializer());

要使用默认值,当与DummyInstanceDeserializer一起使用时,这似乎很好用

public class DummyInstanceCreator implements InstanceCreator<**SubClassDummy**>{
    @Override
    public SubClassDummy createInstance(Type type) {
        return new SubClassDummy("", 2.5, "abc");
    }
}
...
 gsonBuilder.registerTypeAdapter(SubClassDummy.class,new DummyInstanceCreator());