1

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 {};
}
aurelius
  • 3,946
  • 7
  • 40
  • 73

1 Answers1

1

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, {.

Franjavi
  • 647
  • 4
  • 14
  • This Kinda works. `[ { "userId": 1, "username": "bas", "friendsWith": [ { "userId": 2, "username": "dave", "friendsWith": [ 1 ] } ] }, 2, { // Other users },` The thing now is that I see the complete 'User' as a friend. But is it possible to display only the ID in the friendsWith array. And the Complete user as the next full object. Kinda like the friendsWith Array of Dave in this case..... – Bas Brunink Dec 11 '19 at 12:38
  • There is an issue with that, you will not be able to deserialize back the friends by default, because Jackson does not know how to deserialize the ID of the friend to the User object (imagine userId:3 that is not in the body of the JSON). You could use a custom serializer and deserializer for it, but still the friends User objects after deserialize will only have the userId. Is that acceptable? Do you need only the IDs of the friends? – Franjavi Dec 11 '19 at 13:06
  • I think this will help definitly thanks for your help – Bas Brunink Dec 11 '19 at 13:16
  • 1
    I was thinking if you need just the IDs you could do: `@JsonProperty("friendsIds") public List getFriendIds() { return friends.stream().map(u -> u.userId).collect(Collectors.toList()); }` and `@JsonIgnore public List friends;`. You would need still to deal with the deserialization manually – Franjavi Dec 11 '19 at 13:26