I am new to gson and am trying to work out how to deserialise a list within an object.
The error message hints at creating an InstanceCreator for Player, which I did. But when implemented, I was finding that the deserialised object contained a list of Players with the name field set to a 'default value' as opposed to picking up the values from the json string. So I am now wondering whether that approach was correct.
This is a simplification of the model I am working on, but highlights the issue...
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; }
...
}
I have a simple test to create a new team with 2 players
Team t = new TeamImpl("teamname");
t.addPlayer(new PlayerImpl("p1"));
t.addPlayer(new PlayerImpl("p2"));
Gson gson = new Gson();
String json = gson.toJson(t);
which creates the following json string:
{"name":"teamname","players":[{"name":"p1"},{"name":"p2"}]}
However, when I deserialize the json string ...
Team t2 = gson.fromJson(json, TeamImpl.class);
I get the following error:
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
Deserialization in the context of Gson means converting a JSON string to an equivalent Java object. In order to do the deserialization, we need a Gson object and call the function fromJson() and pass two parameters i.e. JSON string and expected java type after parsing is finished. Program output.
GSON can use the Object definition to directly create an object of the desired type. While JSONObject needs to be parsed manually.
Gson is typically used by first constructing a Gson instance and then invoking toJson(Object) or fromJson(String, Class) methods on it. Gson instances are Thread-safe so you can reuse them freely across multiple threads.
Both Gson and Jackson are good options for serializing/deserializing JSON data, simple to use and well documented. Advantages of Gson: Simplicity of toJson/fromJson in the simple cases. For deserialization, do not need access to the Java entities.
Gson clearly reports that it cannot instantiate an interface. In order to make it possible, you can register a custom type adapter that would know how to serialize an instance and deserialize it back.
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);
}
}
And then configure your Gson instance (once per application, Gson is immutable and thread-safe):
Gson gson = new GsonBuilder()
.registerTypeAdapter(Player.class, interfaceSerializer(PlayerImpl.class))
.registerTypeAdapter(Team.class, interfaceSerializer(TeamImpl.class))
.create();
Note that now even Team t2 = gson.fromJson(json, Team.class);
would work (no TeamImpl.class
.
Maybe opinion-based, but... I strongly recommend you not to bind to interfaces in Gson. Gson's only responsibility is serializing and deserializing from and to your business/value objects that your application work with. Take a look at the Data Transfer Object pattern that describes the problem and suggests segration for objects that transfer data to and from your application. Having this, you could drop away interfaces from your DTOs so you won't need adapters like that and don't care how the DTO classes are declared and annotated. Knowing that you might have TeamDto
and PlayerDto
that could even not implement your interfaces, but bind data clearly, say, class TeamDto { private List<PlayerDto> }
. DTOs could be easily converted to trivial implementations, and be easily constructed from them as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With