简体   繁体   中英

Spring Boot API response returns repeating nested JSON

I have a User model and a TodoItem model where the TodoItem model has a primary key to the User model with a user_id @joincolumn. My issue is the response I get from the getUsers API after I add an item. It creates this super long nested JSON where it repeats itself over and over again. I feel like I'm not handling the primary key case properly.

TodoController.java

@RestController
@RequestMapping("/api")
public class TodoController {

@Autowired
private TodoRepository todoRepository;

@PostMapping("/addItem")
public TodoItem addTodoItem(@RequestBody TodoItem todoItem) {
    return todoRepository.save(todoItem);
}

User.java

@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@Column(name = "name")
private String name;

@Column(name = "password")
private String password;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<TodoItem> todos;

public User() {
}

public User(String name, String password, List<TodoItem> todos) {
    this.name = name;
    this.password = password;
    this.todos = todos;
}
// setter and getters

TodoItem.java

@Entity
@Table(name = "todo_item")
public class TodoItem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;

@Column(name = "todo")
private String todo;

@Column(name = "completed")
private boolean completed;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

public TodoItem() {
}

public TodoItem(String todo, boolean completed) {
    this.todo = todo;
    this.completed = completed;
}
// setters and getters

Add Item Request

{
  "todo": "blahblah",
  "completed": false,
  "user": {
      "id": 6
  }
}

Add Item Response

{
  "id": 26,
  "todo": "blahblah",
  "completed": false,
  "user": {
      "id": 6,
      "name": null,
      "password": null,
      "todos": null
  }

}

So already I don't like the way the response is given, why is name, pass, and todos null when the user with id 6 exists, also I just passed it a todoitem, so why is todo null. The database populates properly, it's just that the response seems wrong. And then I think it ties into the main problem I have which is here; this is after I add item to a user:

Get Users Response

[
{
    "id": 6,
    "name": "joe",
    "password": "pass",
    "todos": [
        {
            "id": 26,
            "todo": "blahblah",
            "completed": false,
            "user": {
                "id": 6,
                "name": "joe",
                "password": "pass",
                "todos": [
                    {
                        "id": 26,
                        "todo": "blahblah",
                        "completed": false,
                        "user": {
                            "id": 6,
                            "name": "joe",
                            "password": "pass",
                            "todos": [
                                {
                                    "id": 26,
                                    "todo": "blahblah",
                                    "completed": false,
                                    "user": {
                                        "id": 6,
                                        "name": "joe",
                                        "password": "pass",
                                        "todos": [
                                            {
                                                "id": 26,
                                                "todo": "blahblah",
                                                "completed": false,
                                                "user": {
                                                    "id": 6,
                                                    "name": "joe",
                                                    "password": "pass",
                                                    "todos": [
                                                        {
                                                            "id": 26,
                                                            "todo": "blahblah",

And it just continues like that for literally thousands of lines. Even though the response is crazy, the database updates properly, but the API calls can take a while due to this issue

In your TodoItem.java, remove the getter for the User property.

Make sure that you only have the setter for user property in your TodoItem.java.

Essentially, when Spring forms the response, it is doing a ".toString()" like method to map the entity in to a JSON object to pass to the front end.

You have a bidirectional association between your entities, so when the mapper goes in to the user it maps all the todos and because those todos all have a relationship with the user it then gets the user ...and again...and again and loop of overflow death.

The "best" way and is common is you should make a DTO class which you construct.

UserTodoDTO :


//Lombok Getter/Setter/ToString and All Args Constructor.
@ToString
@Getter
@Setter
@AllArgsConstructor
public class UserTodoDTO {

    private long id;
    @JsonProperty("name")
    private String username;
    private List<TodoItem> todoItems;

}


//Pretend this is full of your 'users'.
List<User> usersFromDatabaseAsEntity = new ArrayList<>();

//Return these and the serialisation will not occur.
final List<UserTodoDTO> viewsOfUser = usersFromDatabaseAsEntity
        .stream()
        .map(entity -> new UserTodoDTO(entity.getId(), entity.getName(), entity.getTodos()))
        .collect(Collectors.toList());

Just be aware, if you do log.info(user) it will do the same thing. The way to avoid this (there are others) is to add a @JsonIgnore to one side of the relationship (like on the @ManyToOne-Users on the Todos) or override the toString() for the Todo.

Changes to TodoItem


    //Will not be mapped to JSON so stops the loop of death.
    @JsonIgnore
    //Lombok can make the toString() for you but need to
    // tell it to ignore this field to stop loop of death.
    @ToString.Exclude
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    
    //If not using lombok and you are doing TodoItem.toString() somewhere...
    //Remove the user from toString().
    @Override
    public String toString() {
        return "TodoItem{" +
                "id=" + id +
                ", todo='" + todo + '\'' +
                ", completed=" + completed +
                '}';
    }

The answer is you have mapped each other entity class,so when you get Todoitem entity and User gets pickedup and again the Todoitem and it goes on.You don't have to map each and every entities.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM