3

I wrote this class and i need to serialize the field gameTimer, whose type is the interface TimerP.

public class PlayerImpl implements Player {

@Expose
private final String nickname;
@Expose
private final TimerP gameTimer;
@Expose
private final int finalScore;

To solve this problem I wrote an interfaceAdapter:

public class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {

private static final String CLASSNAME = "CLASSNAME";
private static final String DATA = "DATA";

@Override
public T deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
    JsonObject jsonObject = json.getAsJsonObject();
    JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
    String className = prim.getAsString();
    Class<?> c = this.getObjectClass(className);
        return context.deserialize(jsonObject.get(DATA), c);
}

@Override
public JsonElement serialize(final T src, final Type typeOfSrc, final JsonSerializationContext context) {
    JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty(CLASSNAME, src.getClass().getName());
    jsonObject.add(DATA, context.serialize(src));
    return jsonObject;
}

/****** Helper method to get the className of the object to be deserialized. *****/
private Class<?> getObjectClass(final String className) {
    try {
        return Class.forName(className);
        } catch (ClassNotFoundException e) {
            //e.printStackTrace();
            throw new JsonParseException(e.getMessage());
        }
}

but it gives this exception when trying to serialize the object:

Caused by: java.lang.StackOverflowError
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:104)
at com.google.gson.reflect.TypeToken.<init>(TypeToken.java:72)
at com.google.gson.reflect.TypeToken.get(TypeToken.java:296)
at com.google.gson.Gson.toJson(Gson.java:696)
at com.google.gson.Gson.toJsonTree(Gson.java:597)
at com.google.gson.Gson.toJsonTree(Gson.java:576)
at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.serialize(TreeTypeAdapter.java:155)
at common.InterfaceAdapter.serialize(InterfaceAdapter.java:33)
at common.InterfaceAdapter.serialize(InterfaceAdapter.java:15)
at com.google.gson.internal.bind.TreeTypeAdapter.write(TreeTypeAdapter.java:81)
at com.google.gson.Gson.toJson(Gson.java:704)
at com.google.gson.Gson.toJsonTree(Gson.java:597)
at com.google.gson.Gson.toJsonTree(Gson.java:576)
at com.google.gson.internal.bind.TreeTypeAdapter$GsonContextImpl.serialize(TreeTypeAdapter.java:155)

This is the class where I read and write on the file:

public class LeaderboardImpl implements Leaderboard {

private final File directory = new File(System.getProperty("user.home"), ".unitype");
private final File file = new File(directory, FILE_NAME);
private static final String FILE_NAME = "unitype.json";

/**
 * Returns the instance of this class.
 * 
 * @return the instance of this class
 */
public static LeaderboardImpl getLeaderboard() {
    return LazyHolderLeaderboard.SINGLETON;
}

/**
 * {@inheritDoc}
 */
@Override
public void addPlayer(final Player p) {
    final Gson gson = new GsonBuilder()
                        .excludeFieldsWithoutExposeAnnotation()
                        .registerTypeHierarchyAdapter(TimerP.class, new InterfaceAdapter<TimerP>())
                        .create();
    final List<Player> playersList = this.getPlayersList();
    playersList.add(p);
    final String json = gson.toJson(playersList);
    this.checkFile();
    try (FileWriter writer = new FileWriter(file)) {
        writer.write(json);
        writer.flush();
    } catch (JsonIOException | IOException e) {
        e.printStackTrace();
    }
}

/**
 * {@inheritDoc}
 */
@Override
public List<Player> getPlayersList() {
    final Gson gson = new GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .registerTypeHierarchyAdapter(TimerP.class, new InterfaceAdapter<TimerP>())
            .create();
    List<Player> playersList = new ArrayList<>();
    this.checkFile();
    try (JsonReader jsonReader = new JsonReader(new FileReader(file))) {
        final JsonElement js = JsonParser.parseReader(jsonReader);
        if (js.isJsonObject()) {
            final JsonObject jsonObject = js.getAsJsonObject();
            playersList.add(gson.fromJson(jsonObject, PlayerImpl.class));
        } else if (js.isJsonArray()) {
            playersList = gson.fromJson(js,
                    new TypeToken<List<PlayerImpl>>() { }.getType());
        }
    } catch (JsonSyntaxException | JsonIOException | IOException e) {
        e.printStackTrace();
    }
    return playersList;
}

I tried everything but I could't find any solution and I don't understand why it doesn't work

coolitos
  • 39
  • 2

2 Answers2

0

Much too late for the poster, but maybe for anyone who also stumbles into this pitfall (like me). This solution is also proposed on some blogs on the internet. However, they use registerTypeAdapter instead of registerTypeHierarchyAdapter.

Interesting note: There are also vice-versa cases where registerTypeAdapter causes stack overflow and registerTypeHierarchyAdapter works, see here.

Werner Thumann
  • 465
  • 4
  • 15
0

use another gson ( new Gson() )

@Override
public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
    final JsonObject wrapper = new JsonObject();
    wrapper.addProperty("type", object.getClass().getName());
    wrapper.add("data", new Gson().toJsonTree(object));
    return wrapper;
}
phil
  • 620
  • 7
  • 12