【问题标题】:How to solve "Could not write JSON: Infinite recursion (StackOverflowError)" in Kotlin using Data Classes?如何使用数据类在 Kotlin 中解决“无法编写 JSON:无限递归 (StackOverflowError)”?
【发布时间】:2021-06-16 20:06:44
【问题描述】:

我知道该主题中有几篇文章,但我只是找不到使用 Kotlin 数据类的文章。因此,我正在尝试使用 Spring Boot 在 Kotlin 中使用 H2 数据库制作 REST API,并且我也在使用 Postman。我的类的一些属性具有 List 类型。每次我尝试在 Postman 中为这些列表添加一些值然后尝试获取结果时,都会出现以下错误:

enter image description here

我有三个班级:

食谱.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" } }

类别.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" } }

成分.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)

【问题讨论】:

    标签: json spring-boot kotlin postman h2


    【解决方案1】:

    您可以按照@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"}]}
    

    【讨论】:

    • 我仍然有相同的结果(更新了那里的代码)。所以它只是以“null”或[]返回。我是否必须更改响应文件或服务/资源文件中的某些内容?
    • 我不确定。也许这是Recipe::toRecipeResponse 方法的问题,或者由于其他一些问题,您没有从数据库中获取数据。我很高兴我的建议将通过无限递归解决您的原始问题。如果我是你,我会: 1. 尝试手动创建RecipeResponse 并查看它是否产生非空列表。 2. 检查RecipeService返回的Recipe对象是否包含任何类别。
    【解决方案2】:

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

    @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 制定您的响应,然后返回它。

    编辑:

    
    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。成分.getDescription() 并且可以生成响应。

    【讨论】:

    • 您能否更具体地了解一下 ObjectNode 部分?我看到现在错误没有发生,但是在 Postman 中我得到了空列表。
    • 我认为您正在使用 @JsonIgnore 注释每个字段,这就是您返回空结果的原因。因为响应忽略了每个字段。所以只需用 JsonIgnore 注释您不想出现的字段以及递归字段(即 OneToMany、ManyToOne 和 ManyToMany 等)
    猜你喜欢
    • 2020-02-12
    • 1970-01-01
    • 2020-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 2012-09-16
    • 1970-01-01
    相关资源
    最近更新 更多