杰克逊 Kotlin - 反序列化 JsonNode

3

问题

我有一个JSON内容的字符串,首先想要使用Jackson编程方式遍历它。然后,当我获得感兴趣的节点时,我想要对其进行反序列化。

我的尝试

我已经成功地使用mapper.readValue反序列化字符串,但现在我想在jsonNode上执行这样的操作,而不是在字符串上。

  • jackson-core:2.9.9
  • jackson-module-kotlin:2.9.9
  • Kotlin 1.3.41
  • kotlin-stdlib-jdk8:1.3.41

代码

package somepackage

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.treeToValue

fun main() {
    val mapper = ObjectMapper().registerModule(KotlinModule())

    readValueWorksFine(mapper)
    treeToValueFails(mapper)
}

fun treeToValueFails(mapper: ObjectMapper) {
    val fullJsonContent = """
            [{
                    "product_id":123, 
                    "Comments":
                        [{
                            "comment_id": 23, 
                            "message": "Hello World!"
                        }]
            }]        
        """.trimIndent()

    // Traverse to get the node of interest
    val commentsNode: JsonNode = mapper.readTree(fullJsonContent).get(0).get("Comments")

    // Deserialize
    val comments: List<Comment> = mapper.treeToValue<List<Comment>>(commentsNode)

    // The line below fails. (I would have expected the exception to be thrown in the line above instead.
    // Exception:
    // Exception in thread "main" java.lang.ClassCastException: class
    // java.util.LinkedHashMap cannot be cast to class somepackage.Comment (java.util.LinkedHashMap is in module
    // java.base of loader 'bootstrap'; somepackage.Comment is in unnamed module of loader 'app')
    for (comment: Comment in comments) { // This line fails
        println(comment.comment_id)
        println(comment.message)
    }
}

fun readValueWorksFine(mapper: ObjectMapper) {
    val commentsJsonContent = """
            [{
                "comment_id": 23, 
                "message": "Hello World!"
            }]
        """.trimIndent()

    val comments1: List<Comment> = mapper.readValue<List<Comment>>(commentsJsonContent)
    for (comment in comments1) {
        println(comment)
    }
}

data class Comment(val comment_id: Long, val message: String)

异常/输出

上述代码会导致以下异常/输出:

Comment(comment_id=23, message=Hello World!)
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class somepackage.Comment (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; somepackage.Comment is in unnamed module of loader 'app')
    at somepackage.TKt.treeToValueFails(T.kt:39)
    at somepackage.TKt.main(T.kt:13)
    at somepackage.TKt.main(T.kt)
1个回答

4

问题原因

尽管ObjectMapper.treeToValue是一个具有reified泛型参数的Kotlin内联扩展函数(这意味着泛型在运行时保留),但它调用Java的ObjectMapper.treeToValue(TreeNode, Class<T>)方法。传递作为Class<T>的值将会失去对于像List<Comment>这样的泛型类型的泛型类型信息,这是由于类型擦除导致的。

所以treeToValue可以用于:

mapper.treeToValue<Comment>(commentNode)

但不适用于:
mapper.treeToValue<List<Comment>>(commentsNode)

请注意,ObjectMapper包含多个带有@SuppressWarnings注释的方法,这会导致一些问题在编译时不会出现,但在运行时出现。
解决方案1-使用 convertValue() 这是最好的解决方案。它使用Kotlin扩展函数 ObjectMapper.convertValue
val commentsNode = mapper.readTree(fullJsonContent).get(0).get("Comments")
val comments = mapper.convertValue<List<Comment>>(commentsNode)

解决方案2 - 使用ObjectReader

这个解决方案不使用jackson-module-kotlin扩展函数。

val reader = mapper.readerFor(object : TypeReference<List<Comment>>() {})
val comments: List<Comment> = reader.readValue(commentsNode)

方案三 - 在 map 中反序列化

由于 Kotlin 扩展函数 treeToValue 无法处理非泛型类型,因此您可以首先将节点作为 JsonNode 列表获取,然后将每个 JsonNode 映射到 Comment 对象。

但是,不幸的是您不能直接返回 mapper.treeToValue(it),因为它会导致类型推断编译错误。

val commentsNode = mapper.readTree(fullJsonContent).get(0).get("Comments")
val comments = commentsNode.elements().asSequence().toList().map {
    val comment: Comment = mapper.treeToValue(it)
    comment
}

今天刚遇到这个问题,根据以下答案,我能够使用以下代码:val node: JsonNode = <包含 MyClass 列表的 JsonNode> val parsedList = node.map{ mapper.treeToValue(it) } - Mike Emery

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接