3

I am trying to deserialize a json string which contains interfaces and hashmaps which has interface type and lists containing interface type into a java object using Gson. But I am getting

java.lang.RuntimeException: Unable to invoke no-args constructor for interface com.abc.Dummy . Register an InstanceCreator with Gson for this type may fix this problem.

I tried to register the type adapter for an instance creator by instantiating the constructor for implementing class with dummy values but the dummy values are not being overwritten with the deserialized values?

Dummy is the interface and SubClassDummy is the implementing class.

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);

Can anyone help with resolving this issue? I cannot modify the java object I am trying to deserialize into. Its a thirdparty class. So can't add any annotations or do any modifications to that class. How can I make gson overwrite the dummy values with deserialized values? I cannot even make any changes to how the java object is serialized into a json string. I just have work with the json string given to deserialize it into a specific java object.

Sam Berry
  • 7,394
  • 6
  • 40
  • 58
  • Can you show us your json ? I can create a sample for you – hurricane Apr 20 '15 at 06:00
  • Its a really huge json string and I don't thing I can post it in here. Its almost like 58000 in length. As an example you can consider json string which has a car interface which has a list of items where items is an interface and the items in turn has a list of enums and interfaces in it and the car also has a hashmap of type offers where offers is an interface etc.... – gowthami muddana Apr 20 '15 at 06:41
  • Did you try create all classes on your project with json struct ? http://json2csharp.com/ You can use ths for java. – hurricane Apr 20 '15 at 07:07
  • No. I used JsonEditorOnline and that seems to display the java object properly. If I use json2csharp then its saying that my json is invalid. – gowthami muddana Apr 20 '15 at 16:28

3 Answers3

0

java.lang.RuntimeException: Unable to invoke no-args constructor for interface com.abc.Dummy

Your Dummy need to be a class (not interface) and requeries a default / empty no args constructor:

public class Dummy {

    public Dummy (){
    }

    [...] // your code
}

Some explanation:

Json parser creates an instance of Dummy in this way:

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

Then using reflection API instance will be initialized. So if your class don't implement a default constructor Dummy.class.newInstance() will fail.

alex
  • 8,904
  • 6
  • 49
  • 75
  • Is there any other workaround for it? I cannot change the interface to be a class. I do have a constructor defined for the implementing class but its not a no-arg constructor. It takes some parameters. Can you suggest any other libraries other than Gson that can work in my case? – gowthami muddana Apr 20 '15 at 14:28
  • Create a new `class DummyImpl implements Dummy` with empty constructor and it should work. – alex Apr 20 '15 at 18:06
  • If I create DummyImpl with empty constructor then it will return me an instance of DummyImpl and not SubClassDummy. What I want is an instance of SubClassDummy. – gowthami muddana Apr 20 '15 at 22:08
0

Following the OP's question i wrote the JUnit test below. It shows how things should work in principle.

But: Since the interfaces needs to use getters/setters things will not work out of the box ...

issue https://github.com/google/gson/issues/232 is still open so this fails ...

See also Why does GSON use fields and not getters/setters? and https://sites.google.com/site/gson/gson-design-document: "We intend to enhance Gson in a latter version to support properties as an alternate mapping for indicating Json fields. For now, Gson is fields-based." - it's still an intention as of 2018.

You can try the workarounds described in https://stackoverflow.com/a/16400945/1497139

For this use case you might be better off using a different JSON library e.g. Jackson, or JaxB e.g EclipseLink Moxy.

For FastJson I created the test below which fails.

JUnit Test for gson

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 dependency used:

   <!-- 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>

JUnit Test for fastjson

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 dependency used:

  <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.54</version>
    </dependency>
Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186
0

To deserialize an interface, this made the trick for me with gson 2.8.5 - You need to also define 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());

To use default values this seems to work fine when used together with 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());
brunesto
  • 430
  • 3
  • 11