如何使用数据 类 在 Kotlin 中解决 "Could not write JSON: Infinite recursion (StackOverflowError)"?

How to solve "Could not write JSON: Infinite recursion (StackOverflowError)" in Kotlin using Data Classes?

我知道该主题中有多个帖子,但我找不到使用 Kotlin 数据 类 的帖子。因此,我正在尝试使用 Spring Boot 在 Kotlin 中使用 H2 数据库创建 REST API,并且我也在使用 Postman。我的 类 的某些属性具有列表类型。每次我尝试在 Postman 中向这些列表添加一些值然后尝试获取结果时,我都会收到以下错误:

enter image description here

我有三个类:

Recipe.kt:

@Entity
data class Recipe(
    @Id
    @SequenceGenerator(name = RECIPE_SEQUENCE, sequenceName = RECIPE_SEQUENCE, initialValue = 1, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0,
    val name: String,
    var cookTime: String?,
    var servings: String?,
    var directions: String?,
    @OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
    @JsonManagedReference
    var ingredient: List<Ingredient>?,
    @ManyToMany
    @JsonManagedReference
    @JoinTable(
        name = "recipe_category",
        joinColumns = [JoinColumn(name = "recipe_id")],
        inverseJoinColumns = [JoinColumn(name = "category_id")]
    )
    var category: List<Category>?,
    @Enumerated(value = EnumType.STRING)
    var difficulty: Difficulty?
    ) { companion object { const val RECIPE_SEQUENCE: String = "RECIPE_SEQUENCE" } }

Category.kt

    @Entity
data class Category(
    @Id
    @SequenceGenerator(name = CATEGORY_SEQUENCE, sequenceName = CATEGORY_SEQUENCE, initialValue = 1, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    var name: String,
    @ManyToMany(mappedBy = "category")
    @JsonBackReference
    var recipe: List<Recipe>?
) { companion object { const val CATEGORY_SEQUENCE: String = "CATEGORY_SEQUENCE" } }

Ingredient.kt

    @Entity
data class Ingredient(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    var description: String?,
    var amount: BigDecimal?,
    @ManyToOne
    @JsonBackReference
    var recipe: Recipe?,
    var unitOfMeasure: String?
)

RecipeResponse.kt

data class RecipeResponse (var id:Long,
                           var name:String,
                           var cookTime:String?,
                           var servings:String?,
                           var directions:String?,
                           var ingredient:List<Ingredient>?,
                           var category: List<Category>?,
                           var difficulty: Difficulty?)

RecipeResource.kt

@RestController
@RequestMapping(value = [BASE_RECIPE_URL])
class RecipeResource(private val recipeManagementService: RecipeManagementService)
{

    @GetMapping
    fun findAll(): ResponseEntity<List<RecipeResponse>> = ResponseEntity.ok(this.recipeManagementService.findAll())

RecipeManagementService.kt

@Service
class RecipeManagementService (@Autowired private val recipeRepository: RecipeRepository,
                               private val addRecipeRequestTransformer: AddRecipeRequestTransformer) {

    fun findAll(): List<RecipeResponse> = this.recipeRepository.findAll().map(Recipe::toRecipeResponse)

您必须在@ManyToOne 和@OneToMany 字段上添加@JsonIgnore。 Spring 将忽略那些 returning 该对象的字段。 外汇

@Entity
data class Ingredient(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    var description: String?,
    var amount: BigDecimal?,
    @ManyToOne
    @JsonIgnore
    var recipe: Recipe?, // This field will not be returned in JSON response.
    var unitOfMeasure: String?
)

在这里,还要注意是否要在响应中包含此 ManyToOne 或 OneToMany 关系的某些字段。您必须使用 ObjectNode 来制定您的响应,然后 return 它。

编辑:


ObjectMapper objectMapper = new ObjectMapper();

@RestController
public ResponseEntity<String> someFunction() throws Exception {
    ObjectNode msg = objectMapper.createObjectNode();
    msg.put("success", true);
    msg.put("field1", object.getValue1());
    msg.put("field2", object.getValue2());
    return ResponseEntity.ok()
        .header("Content-Type", "application/json")
        .body(objectMapper.writerWithDefaultPrettyPrinter()
        .writeValueAsString(res));
}

此处的代码在 java 中,您可以将其转换为 Kotlin,我认为它是一样的。在这里,您可以编写自己的对象名称 ex 而不是 object。 ingredient.getDescription() 并可以生成响应。

您可以按照@Akash 的建议使用@JsonIgnore,但结果不会在响应json 中包含类别字段。对于单个 recipe 对象响应将类似于:

{"id":1,"name":"recipe"}

您可以改用 @JsonManagedReference@JsonBackReference。这样您将打破无限循环,但仍会生成 category 列表。

您的模型将类似于:

data class Recipe(
    var id: Long = 0,
    val name: String,
    @JsonManagedReference
    var category: List<Category>,
)

data class Category(
    val id: Long = 0,
    var name: String,
    @JsonBackReference
    var recipe: List<Recipe>
)

这将生成一个 json:

{"id":1,"name":"recipe","category":[{"id":1,"name":"cat"}]}