【问题标题】:Jackson parses a map of strings to a declared class as a map of strings to maps. How do I make it create objects of the declared class instead?Jackson 将字符串映射解析为声明的类作为字符串映射到映射。如何让它创建声明类的对象?
【发布时间】:2020-08-31 00:06:26
【问题描述】:

我正在使用 jackson-module-kotlin:2.11.2 来解析一些 YAML。我正在尝试生成包含地图的对象,其值是我声明的类的对象。此映射包含的值是 HashMaps。

这是我的声明:

import com.fasterxml.jackson.module.kotlin.readValue

object Parser {
    // ObjectMapper is thread safe as long as we don't mess with the config after this declaration.
    val mapper: ObjectMapper = ObjectMapper(YAMLFactory()).registerKotlinModule()
        .registerModule(JavaTimeModule())
        .registerModule(nullMapDeserialiserModule)
        .registerModule(SimpleModule().setDeserializerModifier(ValidatingDeserializerModifier()))
        // When parsing timestamps, we don't want to lose the offset information
        .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
        // We would prefer an error if we're trying to store a float in an int
        .disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
        // If a primitive field (like Int) is non-nullable (as in the Kotlin meaning), then we don't want nulls being converted to 0
        .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
        // Because enums could change order in future versions (if we keep them in lexicographic order, for example),
        // we don't want the client to expect that they can give us the ordinal value of the enum.
        .enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
        // When serialising schedule expressions, don't include null values
        .setSerializationInclusion(JsonInclude.Include.NON_NULL)

    @Throws(JsonProcessingException::class)
    inline fun <reified T: Any> deserialise(yaml: String): T = mapper.readValue(yaml)

}

data class ListValue (
    val listValueKey: String,
    val someOtherValue: Int
)

data class ExpectedValue (
    val expectedValueKey: String,
    val list: List<ListValue>
)

data class TestClass (
    val testClassKey: String,
    @param:JsonDeserialize(contentAs = ExpectedValue::class)
    val testMap: Map<String, ExpectedValue>
)

这是我的测试用例:

@Test
fun `map parse test`() {
    val testObj: TestClass? = RuleParser.deserialise(
        //language=YAML
        """
          testClassKey: the-key
          testMap:
              key1:
                expectedValueKey: subKey1
                list: 
                  - listValueKey: someKey1
                    someOtherValue: 5
                  - listValueKey: anotherKey1
                    someOtherValue: 6
              key2:
                expectedValueKey: subKey2
                list: 
                  - listValueKey: someKey2
                    someOtherValue: 7
                  - listValueKey: anotherKey2
                    someOtherValue: 8
        """
    )

    assertTrue(testObj is TestClass)
    assert(testObj.testMap is HashMap)
    assertNotNull(testObj.testMap["key1"])
    assert(testObj.testMap["key1"] is ExpectedValue)
    assertEquals(
        ExpectedValue(
            expectedValueKey = "subKey1",
            list = listOf(ListValue("someKey1", 5), ListValue("anotherKey1", 6))
        ),
        testObj.testMap["key1"]
    )
}

目前,此测试在最终断言上失败,并出现以下错误

Expected :ExpectedValue(expectedValueKey=subKey1, list=[ListValue(listValueKey=someKey1, someOtherValue=5), ListValue(listValueKey=anotherKey1, someOtherValue=6)])
Actual   :{expectedValueKey=subKey1, list=[{listValueKey=someKey1, someOtherValue=5}, {listValueKey=anotherKey1, someOtherValue=6}]}

这显然不是我所期望的。如果我改为解析已声明类的列表,则可以正常工作(示例测试如下)。

@Test
fun `list parse test`() {
    val testObj: ExpectedValue? = RuleParser.deserialise(
        //language=YAML
        """
            expectedValueKey: subKey1
            list: 
              - listValueKey: someKey1
                someOtherValue: 5
              - listValueKey: anotherKey1
                someOtherValue: 6
        """
    )

    assertTrue(testObj is ExpectedValue)
    assertTrue(testObj.list[0] is ListValue)
    assertEquals(
        ListValue("someKey1", 5),
        testObj.list[0]
    )
}

所以我有点惊讶可以以这种方式解析通用列表,但不能解析地图。如何让 Jackson 创建我期望的地图值,而不是 HashMaps?

【问题讨论】:

    标签: kotlin jackson jackson-databind


    【解决方案1】:

    您的反序列化程序功能错误。您必须在readValue 方法中使用具体化的泛型类型:

    inline fun <reified T: Any> deserialise(yaml: String): T = mapper.readValue(yaml, T::class.java)
    
    

    【讨论】:

    • 另一种方法是使用 Jackson Kotlin 模块中的 readValue&lt;reified T&gt; 扩展函数。 OP 不清楚该函数是否已导入。
    • 抱歉,是的,我正在调用 Jackson Kotlin 模块中的那个函数。
    【解决方案2】:

    虽然奇怪的是根本需要注解(因为声明类型的列表不需要它),但如果按如下方式使用注解,它确实可以工作:

    @field:JsonDeserialize(`as` = HashMap::class, contentAs = ExpectedValue::class)
    

    一开始并不清楚,因为contentAs 的javadoc 没有提到as 也是必需的。

    【讨论】:

      猜你喜欢
      • 2014-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-30
      • 1970-01-01
      • 2019-09-18
      • 2012-01-23
      • 1970-01-01
      相关资源
      最近更新 更多