使用jackson-module-kotlin反序列化通用类

时间:2018-11-27 12:11:01

标签: generics kotlin jackson

我正在尝试使用jackson-kotlin集成。通常,它工作得很好,但是在反序列化泛型类型时遇到了麻烦。我试图使答案适应以下问题:Jackson - Deserialize using generic class

    // create an object mapper
    val jsonFactory = JsonFactory()
    jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false)
    jsonFactory.configure(JsonParser.Feature.IGNORE_UNDEFINED, true)

    val objectMapper = ObjectMapper(jsonFactory)
    objectMapper.findAndRegisterModules()
    objectMapper.propertyNamingStrategy = PropertyNamingStrategy.SnakeCaseStrategy()
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)

    // simple generic type
    data class Inner(val meaningOfLife: Int)
    data class Outer<T>(val inner: T)

    val outer = Outer(Inner(42))

    val serialized = objectMapper.stringify(outer, true)
    println(serialized)

    // deserializing does not work using:
    // https://stackoverflow.com/questions/11664894/jackson-deserialize-using-generic-class
    val parsed = objectMapper.readValue<Outer<Inner>>(serialized, objectMapper.typeFactory.constructParametricType(Outer::class.java,Inner::class.java))

这引发异常:

com.fasterxml.jackson.databind.JsonMappingException: Cannot deserialize Class io.inbot.common.ObjectMapperTest$should handle generics$Outer (of type local/anonymous) as a Bean
 at [Source: (String)"{
  "inner" : {
    "meaning_of_life" : 42
  }
}"; line: 1, column: 1]

    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:306)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:268)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:477)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4190)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4009)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3042)
    at io.inbot.common.ObjectMapperTest.should handle generics(ObjectMapperTest.kt:22)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:580)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:716)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:988)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at org.testng.TestRunner.privateRun(TestRunner.java:648)
    at org.testng.TestRunner.run(TestRunner.java:505)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
    at org.testng.SuiteRunner.run(SuiteRunner.java:364)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1208)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1137)
    at org.testng.TestNG.runSuites(TestNG.java:1049)
    at org.testng.TestNG.run(TestNG.java:1017)
    at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
    at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123)
Caused by: java.lang.IllegalArgumentException: Cannot deserialize Class io.inbot.common.ObjectMapperTest$should handle generics$Outer (of type local/anonymous) as a Bean
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.isPotentialBeanType(BeanDeserializerFactory.java:877)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:131)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:411)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
    ... 31 more

显然,kotlin的杰克逊插件无法处理此问题。是否有解决此问题的方法或执行此操作的其他方法?

顺便说一句。 stringify函数是我为ObjectMapper添加的一个简单扩展函数,用于处理样板代码:

/**
 * Serializes [value] to a string. Pretty prints if [pretty] is set.
 */
fun <T> ObjectMapper.stringify(value: T, pretty: Boolean = false): String {
    val bos = ByteArrayOutputStream()
    val writer = OutputStreamWriter(bos, StandardCharsets.UTF_8)
    if(pretty) {
        writerWithDefaultPrettyPrinter().writeValue(writer,value)
    } else {
        writeValue(writer, value)
    }
    writer.flush()
    bos.flush()
    return bos.toByteArray().toString(StandardCharsets.UTF_8)
}

更新 @ jayson-minard给出的答案中的代码有效。事实证明,与我的代码的主要区别在于我在测试方法中定义了数据类。将它们移到顶层可以解决问题。首先,将数据类放入函数中不是一个好主意。

1 个答案:

答案 0 :(得分:2)

使用Jackson的2.9.6,然后使用Jackson的主分支2.9.8以及当前的Jackson-Kotlin模块,我添加了此测试用例,该用例可同时为您的代码版本和代码的更简洁的习惯版本。

请注意,我也将stringify方法更新为更惯用的方法,但这不会影响测试。

您对readValue的呼叫也比必要的更为复杂。更改:

val parsed = objectMapper.readValue<Outer<Inner>>(serialized, objectMapper.typeFactory.constructParametricType(Outer::class.java, Inner::class.java))

简单来说:

val parsed = objectMapper.readValue<Outer<Inner>>(serialized)    

这是完全通过的考试:

class TestStackOverflow53499407 {
    data class Inner(val meaningOfLife: Int)
    data class Outer<T>(val inner: T)

    fun <T> ObjectMapper.stringify(value: T, pretty: Boolean = false): String {
        StringWriter().use { writer ->
            if (pretty) {
                writerWithDefaultPrettyPrinter().writeValue(writer, value)
            } else {
                writeValue(writer, value)
            }
            return writer.toString()
        }
    }

    @Test
    fun test53499407_cleanTest() {
        val outer = Outer(Inner(42))
        val objectMapper = jacksonObjectMapper()

        val serialized = objectMapper.stringify(outer, true)
        println(serialized)

        val parsed = objectMapper.readValue<Outer<Inner>>(serialized)
        assertEquals(42, parsed.inner.meaningOfLife)
    }

    @Test
    fun test53499407_idiomatic_tweek() {
        val outer = Outer(Inner(42))

        val jsonFactory = JsonFactory()
        jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false)
        jsonFactory.configure(JsonParser.Feature.IGNORE_UNDEFINED, true)

        val objectMapper = ObjectMapper(jsonFactory)
        objectMapper.findAndRegisterModules()
        objectMapper.propertyNamingStrategy = PropertyNamingStrategy.SnakeCaseStrategy()
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)

        val serialized = objectMapper.stringify(outer, true)
        println(serialized)

        // This line changed to be idiomatic
        val parsed = objectMapper.readValue<Outer<Inner>>(serialized)
        assertEquals(42, parsed.inner.meaningOfLife)
    }

    @Test
    fun test53499407_as_written_in_stackoverflow() {
        val outer = Outer(Inner(42))

        val jsonFactory = JsonFactory()
        jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false)
        jsonFactory.configure(JsonParser.Feature.IGNORE_UNDEFINED, true)

        val objectMapper = ObjectMapper(jsonFactory)
        objectMapper.findAndRegisterModules()
        objectMapper.propertyNamingStrategy = PropertyNamingStrategy.SnakeCaseStrategy()
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)

        val serialized = objectMapper.stringify(outer, true)
        println(serialized)

        // deserializing does not work using:
        // https://stackoverflow.com/questions/11664894/jackson-deserialize-using-generic-class
        val parsed = objectMapper.readValue<Outer<Inner>>(serialized, objectMapper.typeFactory.constructParametricType(Outer::class.java, Inner::class.java))
        assertEquals(42, parsed.inner.meaningOfLife)
    }
}

enter image description here