0
votes

I am having trouble to figure out why saving the following object to a .json file with Gson throws a StackOverflowError. Am I having a circular reference somewhere?

This is the object I am trying to save with Gson:

public class LocationConfig {

    private HashSet<Warp> warps = new HashSet<>();

    public LocationConfig() {
        warps.add(new Warp("placeholder", false, new Location(Bukkit.getWorld("world"), 0, 0, 0)));
    }

    // getter and setter

}

The class LocationConfig uses the following Warp class in its HashSet warps:

public class Warp {

    private String name;
    private boolean ifListed;
    private Location loc;

    public Warp(String name, boolean ifListed, Location loc) {
        this.name = name;
        this.ifListed = ifListed;
        this.loc = loc;
    }

    // getter and setter

}

I am using the following to save an LocationConfig Object:

// config is an object of the type LocationConfig
if (config != null) {

    Gson gson = new GsonBuilder().setPrettyPrinting().create();

    try (FileWriter writer = new FileWriter(path)) {
        gson.toJson(config, writer);
    } catch (IOException e) {
        e.printStackTrace();
    }

}

The error I get while performing gson.toJson(config, writer):

[16:12:42] [Server thread/WARN]: java.lang.StackOverflowError
[16:12:42] [Server thread/WARN]:    at com.google.gson.internal.$Gson$Types.resolve($Gson$Types.java:383)
[16:12:42] [Server thread/WARN]:    at com.google.gson.internal.$Gson$Types.resolve($Gson$Types.java:378)
repeating very often
[16:12:42] [Server thread/WARN]:    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:158)
[16:12:42] [Server thread/WARN]:    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:100)
[16:12:42] [Server thread/WARN]:    at com.google.gson.Gson.getAdapter(Gson.java:423)
[16:12:42] [Server thread/WARN]:    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:115)
[16:12:42] [Server thread/WARN]:    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:164)
[16:12:42] [Server thread/WARN]:    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:100)
[16:12:42] [Server thread/WARN]:    at com.google.gson.Gson.getAdapter(Gson.java:423)
repeating very often

Thank you!

1
Does your Location class contain a reference to Warp ? Can you also describe Location ? - Adwait Kumar
@AdwaitKumar Location definitely does not contain a reference to Warp. Location contains doubles (x,y,z), a Vector describing the direction and a World object (also not referencing to Warp in any way). - Jakob
maybe it contains a reference to config - Karim SNOUSSI
@KarimSNOUSSI Not to LocationConfig. - Jakob
Whether location has this or that it would be good idea to expose the whole class reference tree. Like Location, World. Maybe the possible circular ref is not related to any shown classes anyhow. Maybe it is between some other classes. So, how do possible answerers reproduce this problem? - pirho

1 Answers

0
votes

The problem was, that I was using the Location class of a Minecraft Server API called Spigot. In order to solve the problem I had to use a much simpler Location object than the provided one.

As mentioned in the comments:

Usual suspects for this error are either circular references or unknowingly put complex objects

The solution for me was this SimpleLocation class, because it does not contain any complex references:

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.util.Vector;

public class SimpleLocation {

    private double x;
    private double y;
    private double z;
    private Vector direction;
    private String worldname;

    public SimpleLocation(String worldname, double x, double y, double z, Vector direction) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.direction = direction;
        this.worldname = worldname;
    }

    public SimpleLocation(Location loc) {
        this.x = loc.getX();
        this.y = loc.getY();
        this.z = loc.getZ();
        this.direction = loc.getDirection();
        if (loc.getWorld() != null) {
            this.worldname = loc.getWorld().getName();
        } else {
            throw new NullPointerException("The locations world is null!");
        }
    }

    public Location getLocation() {
        return new Location(Bukkit.getWorld(worldname), x, y, z).setDirection(direction);
    }

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public double getZ() {
        return z;
    }

    public void setZ(double z) {
        this.z = z;
    }

    public Vector getDirection() {
        return direction;
    }

    public void setDirection(Vector direction) {
        this.direction = direction;
    }

    public String getWorldname() {
        return worldname;
    }

    public void setWorldname(String worldname) {
        this.worldname = worldname;
    }

}