Reputation: 81
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
Upvotes: 8
Views: 10451
Reputation: 311
In general, this exception can also be caused when a class uses an interface as a field. Especially when creating tree structures it can be difficult to see what you're doing wrong:
class A implements IA{
IA parent;
Collection<IA> nodes;
}
IA myimpl = gson.fromJson( data_str, A.class );
where IA is the interface of class A. This code is going to throw the above exception.
Upvotes: 0
Reputation: 21115
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.
Upvotes: 13
Reputation: 515
You have this error: Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: com.example.Player
so you should make a list inside TeamImpl
like this `List players = new ArrayList(); Because an interface can't be instantiated in 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());
}
`
Upvotes: 0
Reputation: 4820
Gson works with POJOs and not interfaces during deserialization. Please use the implementation PlayerImpl
instead of interface Player
. There are other libraries like Genson which support the kind of requirement you have.
Upvotes: 0