Search code examples
spring-bootspring-data-jpajacksonjson-view

Spring boot JPA: recursion on JSON-view in a self join relationShip


For a project that I am trying to build, I need a manyToMany relationship From the User Class to the Class User.(Users have Friends and Friends are friends of Users). When trying to get Json out of SpringBoot with JPA. I get a recursive loop on my JSON.(This happens only if two users are friends of each other)

I know what happens but cannot find the solution to the problem. As you can see I'm using different views to 'filter' the view. The question is: How can I stop the recursion?

@Entity
public class User {
    @Id
    @GeneratedValue
    @JsonView(JsonViews.UserView.class)
    private long userId;
    @JsonView(JsonViews.UserView.class)
    private String username;
    @JsonView(JsonViews.UserView.class)
    private String password;
    @JsonView(JsonViews.UserView.class)
    private String email;
    @JsonView(JsonViews.UserView.class)
    private String location;
    //private String avatar;
    @JsonView(JsonViews.UserView.class)
    private int ranking;
    private String UserStatus;

    //Friend Relation Many > Many
    @JsonView({JsonViews.UserView.class, JsonViews.FriendWith.class})
    @ManyToMany()
    private List<User> friendsWith;
    @ManyToMany(mappedBy = "friendsWith")
    private List<User> friendOf;
    @JsonView({JsonViews.UserView.class, JsonViews.Player.class})
    @OneToMany(mappedBy = "user")
    private List<Player> player;

    public User() {
        this.friendsWith = new ArrayList<>();
        this.friendOf = new ArrayList<>();
    }

    public User(String username, String password, String email, String location, int ranking) {
        this();
        //this.userId = userId;
        this.username = username;
        this.password = password;
        this.email = email;
        this.location = location;
        this.ranking = ranking;

    }
// and th usual getters and setters

Stack:

2019-12-10 22:17:52.414 ERROR 1618 --- [nio-8084-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() 
for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.hibernate.collection.internal.PersistentBag[0]->
nl.hva.ewa.stratego.backend.models.User["friendsWith"]->
org.hibernate.collection.internal.PersistentBag[0]->
nl.hva.ewa.stratego.backend.models.User["friendsWith"]->
org.hibernate.collection.internal.PersistentBag[0]->
nl.hva.ewa.stratego.backend.models.User["friendsWith"]->
org.hibernate.collection.internal.PersistentBag[0]->
nl.hva.ewa.stratego.backend.models.User["friendsWith"]->
ETC.... ETC.....

for completeness the JsonViews class:


public class JsonViews {
    public class UserView { };
    public class AComplete extends UserView { };
    public class BComplete extends UserView { };
    public class FriendsWith extends UserView {};
    public class FriendsOf extends UserView {};

    public class Player extends UserView {};
}

Solution

  • Try the @JsonIdentityInfo annotation, where you can tell Jackson what is the POJO id, so it does not repeat it:

    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "userId")
    public class User {
        public long userId;
        public String name;
        public List<User> friends;
    }
    

    That will generate the following JSON:

      {
      "userId" : 11,
      "name" : "A",
      "friends" : [ {
        "userId" : 22,
        "name" : "B",
        "friends" : [ 11, {
          "userId" : 33,
          "name" : "C",
          "friends" : [ ]
        } ]
      } ]
    }
    

    Notice that in the inner user B, the friends list contains just the ID if the user has been already added to the JSON body [ 11, {.