【问题标题】:deserialize generic class using jackson-module-kotlin使用 jackson-module-kotlin 反序列化泛型类
【发布时间】:2019-04-29 04:16:24
【问题描述】:

我正在尝试使用 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 的 jackson 插件不能处理这个问题。是否有解决方法或其他方法?

顺便说一句。 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 的答案中的代码有效。事实证明,与我的代码的主要区别在于我在测试方法中定义了数据类。将它们移到顶层可以解决问题。一开始就将数据类放入函数中是个坏主意。

【问题讨论】:

  • 你有没有在没有你的类型工厂调用的情况下尝试过这个,让readValue扩展函数只是推断出具体的类型?
  • 您使用的是什么版本的 Jackson?您使用的是什么版本的 Jackson-Kotlin 模块?什么版本的 Kotlin?
  • 2.9.7 两者,类型推断在这里不起作用,因为杰克逊无法知道类型是什么。还有 Java 8、kotlin 1.3.10

标签: generics kotlin jackson


【解决方案1】:

使用 Jackson 的 2.9.6 和 Jackson 的 master 分支 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)
    }
}

【讨论】:

  • 感谢您对此进行调查。我尝试使用对象映射器配置的一些变体和更好的字符串化来做你所做的事情,但我总是得到这个奇怪的异常:java.lang.reflect.GenericSignatureFormatError: Signature Parse error: expected ';>;反序列化为非泛型类有效,即字符串没有问题。那么你的设置有什么不同呢? Java 8/kotlin 1.3.10 在这里。
  • 我发现了区别,像我一样将数据类定义放在测试方法中不起作用。将它们移到函数范围之外可以解决问题。甚至我的原始版本也是如此。因此,将反序列化的类定义放在哪里很重要。
猜你喜欢
  • 2012-07-24
  • 2012-10-18
  • 1970-01-01
  • 2020-05-28
  • 2014-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多