1

I am trying to achieve a parent-child relation between some objects and I ran into a bit of trouble.

In my case, I am trying to store objects within other objects (e.g. container stores multiple items or other containers with items). The tricky part is that every object in the storage should be able to tell what it's outermost parent object is. While this seems to work in my in-memory database (using h2 at the moment), trying to get a JSON representation of all my storage items gives this (I return a List<StorageUnit> ):

Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.warehousing.storage.FixedContentsCase["contents"]->java.util.ArrayList[0]->com.warehousing.storage.FixedContentsCase["contents"]->...

Here are the classes:

StorageUnit

@Entity
@Inheritance
public abstract class StorageUnit {

   @Id
   @GeneratedValue(strategy=GenerationType.IDENTITY)
   private Long id;
   @ManyToOne
   private Location location;
   protected Long parentContainerId;
   // <getters & setters> 
   public abstract List<StorageUnit> getContents();
}

FixedContentCase


@Entity
public class FixedContentsCase extends StorageUnit {
    @OneToMany
    private List<Item> items; 

    public FixedContentsCase() {
        super();
        items = new ArrayList<>();
    }
    // <getters & setters> 
    @Override
    public List<StorageUnit> getContents() {
        // Return the case itself and its contents
        List<StorageUnit> contents = new ArrayList<>(Arrays.asList(this));
        for (StorageUnit item : items) 
            contents.addAll(item.getContents());
        return contents;
    }   
}

Item

@Entity
public class Item extends StorageUnit {

    private String description;

    public Item() {
        super();
        this.description = "";
    }
    // <getters & setters> 
    @Override
    public List<StorageUnit> getContents() {
        return Arrays.asList(this);
    }   
}

I have tried to annotate the StorageUnit class with @JsonIgnoreProperties("parentContainerId") but it didn't work. Annotating parentContainerId with @JsonIgnore didn't help either. I also tried annotating the getters instead of the attributes themselves (as per following). Is there a way to work around this or is some kind of design change necessary? Thanks!

bekauz
  • 53
  • 4
  • Yeah im not sure if thats not possible, but what you need to do or atleast what I would do is use DTO pattern, which means you make a class that has all those attributes like "parentName", "childName", "grandChildName", and so on as you see it would be needed, and those would have their getter setter and thats what you will send in your jsons. feel free to ask for clarification on anything – BugsForBreakfast Feb 17 '20 at 15:49
  • What happens if you remove the following `Arrays.asList(this)`? – InsertKnowledge Feb 17 '20 at 15:52
  • @InsertKnowledge I thought that might have been the problem too, but it did not change anything – bekauz Feb 18 '20 at 11:48
  • @BugsForBreakfast I have not heard about the DTO approach before and it would seem like the right approach. However, I chose not to set a limit on how many sub-containers may be stored within a given container. This would make it a bit troublesome to achieve in case I needed to define attributes like "grandgrandChildName". Thanks! – bekauz Feb 18 '20 at 12:44
  • @bekauz Yes man it is the right approach, take it in count for future similar tasks, in this case its true that the naming and extensiveness of the case is kind of tricky to deal with, in this case you should try to define according to the business process what is the max case of childs the json would have, like they should tell you "nah the thing usually wont have more than 3 sub objects", so in that way you make it have maximum 5 childrens. I know my english isn't excellent here but I think you understand me lol, anyways check out the pattern it is very usefull for alot cases c: – BugsForBreakfast Feb 18 '20 at 15:31

2 Answers2

1

Using Jackson this is definitely possible by annotations like @JsonIgnore or the DTO approach BugsForBreakfast mentioned.

I created a jackson MixIn handler to allow dynamic filtering which i use to avoid the boilerplate of DTOs

https://github.com/Antibrumm/jackson-antpathfilter

The examples in the readme should show how it works and if it‘s a possible solution for you.

Martin Frey
  • 10,025
  • 4
  • 25
  • 30
0

Your problem is that you add the storage unit itself to its list of contents, leading to infinite recursion if you traverse the tree downwards. The solution: Use a reference and only serialize the object once, using @JsonIdentityInfo and @JsonIdentityReference:

public class MyTest {

    @Test
    public void myTest() throws JsonProcessingException {
        final FixedContentsCase fcc = new FixedContentsCase();
        fcc.setId(Long.valueOf(1));
        final Item item = new Item();
        item.setId(Long.valueOf(2));
        item.setDescription("item 1");
        fcc.getItems().add(item);
        final ObjectMapper om = new ObjectMapper();
        System.out.println(om.writeValueAsString(fcc));
    }
}

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = false)
class Item extends StorageUnit {

    ...
}

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonIdentityReference(alwaysAsId = false)
class FixedContentsCase extends StorageUnit {

    ...
}

abstract class StorageUnit {

    ...
}
sfiss
  • 2,119
  • 13
  • 19
  • Indeed that was the issue, and using `@JsonIdentityInfo` and `@JsonIdentityReference` worked perfectly. Thanks! – bekauz Feb 18 '20 at 12:33