ShakMan ShakMan - 1 month ago 16
Java Question

Infinite Recursion when convert object to JSON using Jackson

I have this class that I am using to store preferences for a tool. This class uses the toString() to convert itself to JSON. I am getting this infinite recursion error when I am converting the object to JSON, and I cannot figure out why. I do not have any circular references so it is confusing me a bit. Any help would be appreciated.

Here is my class structure:

public class ToolPrefs
{
private boolean isFloating;
private ToolPalettePlacement placement; //this is an enum
private boolean isSelected;
private int dockLocation;
private Point dialogLocation;
private Dimension dialogSize;

public ToolPrefs(){}

public boolean isFloating(){return isFloating; }

public void setFloating(boolean floating){ isFloating = floating; }

public ToolPalettePlacement getPlacement(){ return placement; }

public void setPlacement(ToolPalettePlacement placement){ this.placement = placement;}

public boolean isSelected(){ return isSelected;}

public void setSelected(boolean selected){isSelected = selected;}

public int getDockLocation(){return dockLocation;}

public void setDockLocation(int dockLocation){this.dockLocation = dockLocation; }

public Point getDialogLocation(){return dialogLocation;}

public void setDialogLocation(Point dialogLocation){ this.dialogLocation = dialogLocation;}

public Dimension getDialogSize(){ return dialogSize; }

public void setDialogSize(Dimension dialogSize){this.dialogSize = dialogSize;}


@JsonIgnore @Override
public String toString()
{
String json;
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
try
{
json = ow.writeValueAsString(this);
}
catch (IOException e)
{
e.printStackTrace();
json = "";
}
return json;
}
}


Here is the exception snippet I am receiving:


com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.awt.Point["location"]->java.awt.Point["location"]->java.awt.Point["location"]->java.awt.Point["location"]...)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:518)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:117)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:464)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:504)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:117)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:464)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:504)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:117)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:464)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:504)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:117)

Answer

The problem you're facing is due to the Point class that your Object is embedding.

To solve this issue, you can instruct Jackson to stop at first level of depth, and to implement this solution you can follow these steps:

1) First of all, you must be running the 2.x version of Jackson, I've tested the solution with this:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.8.3</version>
    </dependency>
</dependencies>

2) When you initialise a new instance of the ObjectMapper, do this:

ObjectWriter ow = new ObjectMapper().configure(DEFAULT_VIEW_INCLUSION, true).writer().withDefaultPrettyPrinter();

3) Where you have the Point type, use the annotation:

@JsonView(Point.class) Point dialogLocation;

Jackson will stop at the first level, and the result will be:

{
  "dialogLocation" : {
    "x" : 2.0,
    "y" : 3.0
  }
}

For further documentation, you can see this:

Limit Jackson output to only one level

Enable default view inclusion on Jackson 2.x ObjectMapper

I've tested the solution and it works as long as you're using the 2.x version of Jackson. With 1.x I couldn't manage to have it running yet.