springspring-bootspring-data-jpa

How to stop expanding objects into JSON in spring rest controller's response?


I have been writing an endpoint which responds an user information. User class has id, name, and posts. posts is list of Post class, which includes User so that when I requested the endpoints, the following response returned.

{"id":1,"name":"foo","posts":[{"id":1,"name":"diary","createdAt":null,"user":{"id":1,"name":"foo","posts":...

What I excepted:

{"id": 1, "name": "foo", "posts":[{"id": 1, "name": "diarty", "createdAt": null, "userId": 1}]}

To fix the behaviour, how should I rewrites program?

User.java

@Entity
public class User {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
    public Long id;
    public String name;

    @OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
    public List<Post> posts;
}

Post.java

@Entity
public class Post {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY) 
    public Long id;
    public String name;
    public Date createdAt;

    @ManyToOne
    @JoinColumn(name="user")
    public User user;
}

The endpoint:

@GetMapping("/users/{id}")
public User getUserInfo(@PathVariable Long id) {
  Optional<User> res = userRepo.findById(id);
  if (res.isEmpty()) {
    throw new IllegalArgumentException("no user " + id + " found");
  } else {
    return res.get();
  }
}

Solution

  • Use of DTOs will give us control over the expected response.

    Create PostDTO

    public class PostDTO {
        public Long id;
        public String name;
        public Date createdAt;
        public Long userId;
    
        public PostDTO(Post post) {
            this.id = post.id;
            this.name = post.name;
            this.createdAt = post.createdAt;
            this.userId = post.user.id;
        }
    }
    

    Create UserDTO

    public class UserDTO {
        public Long id;
        public String name;
        public List<PostDTO> posts;
    
        public UserDTO(User user) {
            this.id = user.id;
            this.name = user.name;
            this.posts = user.posts.stream()
                                   .map(PostDTO::new)
                                   .collect(Collectors.toList());
        }
    }
    

    Make the required changes in the controller,

    @GetMapping("/users/{id}")
    public UserDTO getUserInfo(@PathVariable Long id) {
        Optional<User> res = userRepo.findById(id);
        if (res.isEmpty()) {
            throw new IllegalArgumentException("No user " + id + " found");
        } else {
            return new UserDTO(res.get());
        }
    }
    

    The above code changes result in the expected JSON response.

    Additionally, we can use modelmapper, which simplifies the mapping between entities and DTOs.